A map of US railroads, Amtrak stations, and live Amtrak train locations. Data from the National Atlas and Amtrak's MapsEngine feed. Based on Mike Bostock's Swiss Cantons example and the U.S. Atlas project.
Last active
August 29, 2015 13:58
-
-
Save madelfio/10412875 to your computer and use it in GitHub Desktop.
U.S. Rail II
This file contains 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
build/ |
This file contains 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
#!/usr/bin/env node | |
var fs = require("fs"); | |
var idProperty = process.argv[2], | |
collection = JSON.parse(fs.readFileSync("/dev/stdin")), | |
featureIds = {}; | |
collection.features = collection.features.filter(function(feature) { | |
var id = feature.properties[idProperty]; | |
if (id == null) throw new Error("id is required for geouniq"); | |
if (!(id in featureIds)) { | |
featureIds[id] = 1; | |
return true; | |
} | |
}); | |
console.log(JSON.stringify(collection)); |
This file contains 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> | |
<meta charset="utf-8"> | |
<style> | |
.background { | |
fill: none; | |
pointer-events: all; | |
} | |
.boundary { | |
fill: none; | |
stroke: #fff; | |
stroke-linejoin: round; | |
stroke-linecap: round; | |
} | |
.feature--state { | |
fill: #bbb; | |
} | |
.feature--station { | |
fill: white; | |
fill-opacity: .8; | |
stroke: steelblue; | |
stroke-opacity: .8; | |
} | |
.feature--railroad { | |
fill: none; | |
fill-opacity: .8; | |
stroke: #d77; | |
} | |
.feature--train { | |
fill: steelblue; | |
fill-opacity: .8; | |
} | |
.feature--state .active { | |
fill: orange; | |
} | |
.feature--railroad { | |
pointer-events: none; | |
} | |
.train-cell { | |
fill: #eee; | |
fill-opacity: .1; | |
} | |
.label { | |
pointer-events: none; | |
} | |
</style> | |
<body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="http://d3js.org/topojson.v1.min.js"></script> | |
<script src="./railroads.js"></script> |
This file contains 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
.PHONY: all clean | |
GENERATED_FILES = \ | |
railroads.json | |
all: railroads.json | |
clean: | |
rm -rf -- $(GENERATED_FILES) build | |
build/%.tar.gz: | |
mkdir -p $(dir $@) | |
curl 'http://dds.cr.usgs.gov/pub/data/nationalatlas/$(notdir $@)' -o $@.build | |
mv $@.build $@ | |
build/%.shp: | |
rm -rf $(basename $@) | |
mkdir -p $(basename $@) | |
tar -xzm -C $(basename $@) -f $< | |
for file in $(basename $@)/*; do chmod 644 $$file; mv $$file $(basename $@).$${file##*.}; done | |
rmdir $(basename $@) | |
build/states-unfiltered.shp: build/statep010_nt00798.tar.gz | |
build/stations-unfiltered.shp: build/amtrakx010g.shp_nt00823.tar.gz | |
build/railroads.shp: build/railrdl010_nt00800.tar.gz | |
# remove duplicate states for water (e.g., Great Lakes) | |
build/states.shp: build/states-unfiltered.shp ./geouniq | |
@rm -f -- $@ $(basename $@)-unfiltered.json | |
ogr2ogr -f 'GeoJSON' $(basename $@)-unfiltered.json $< | |
./geouniq STATE_FIPS < $(basename $@)-unfiltered.json > $(basename $@).json | |
ogr2ogr -f 'ESRI Shapefile' $@ $(basename $@).json | |
rm -f -- $(basename $@).json $(basename $@).json | |
build/stations.shp: build/stations-unfiltered.shp | |
rm -f $@ | |
ogr2ogr -f 'ESRI Shapefile' -where "STNTYPE = 'RAIL'" $@ $< | |
build/states.json: build/states.shp | |
topojson \ | |
-o $@ \ | |
--id-property=FIPS,STATE_FIPS \ | |
-p name=STATE \ | |
--no-quantization \ | |
-- states=$< | |
build/stations.json: build/stations.shp | |
topojson \ | |
-o $@ \ | |
--no-quantization \ | |
-- stations=$< | |
build/railroads.json: build/railroads.shp | |
topojson \ | |
-o $@ \ | |
--no-quantization \ | |
-- railroads=$< | |
build/%.json: build/%-unmerged.json | |
topojson-merge \ | |
-o $@ \ | |
--in-object=$* \ | |
--out-object=$* \ | |
-- $< | |
railroads.json: build/states.json build/stations.json build/railroads.json | |
topojson \ | |
-o $@ \ | |
--projection 'd3.geo.albersUsa().scale(2000).translate([960, 500])' \ | |
--simplify=.5 \ | |
-- $^ |
This file contains 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
"use strict"; | |
var width = 960, | |
height = 500, | |
translate, | |
scale, | |
json_scale = 0.5, | |
activeState = d3.select(null), | |
activeTrain; | |
var zoom = d3.behavior.zoom() | |
.translate([0, 0]) | |
.scale(1) | |
.scaleExtent([json_scale, json_scale * 16]) | |
.on("zoom", zoomed); | |
var projection = d3.geo.albersUsa() | |
.scale(2000) | |
.translate([width, height]); | |
var path = d3.geo.path() | |
.projection(null); | |
var trainPath = d3.geo.path() | |
.projection(projection); | |
var voronoi = d3.geom.voronoi() | |
.clipExtent([[0, 0], [width / json_scale, height / json_scale]]) | |
.x(function(d) {return d.properties.proj[0];}) | |
.y(function(d) {return d.properties.proj[1];}); | |
var svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
.on("click", stopped, true); | |
svg.append("rect") | |
.attr("class", "background") | |
.attr("width", width) | |
.attr("height", height) | |
.on("click", reset); | |
var g = svg.append("g"); | |
svg.call(zoom) | |
.call(zoom.event); | |
d3.json("railroads.json", function(error, rr) { | |
window.rr = rr; | |
g.append("g") | |
.attr("class", "feature feature--state") | |
.selectAll("path") | |
.data(topojson.feature(rr, rr.objects.states).features) | |
.enter().append("path") | |
.attr("d", path) | |
.on("click", function(d) {if (scale < 1) {clicked(d);}}); | |
g.append("path") | |
.datum(topojson.mesh(rr, rr.objects.states, function(a, b) { return a !== b; })) | |
.attr("class", "boundary boundary--states") | |
.attr("d", path); | |
g.append("g") | |
.attr("class", "feature feature--railroad") | |
.selectAll("path") | |
.data(topojson.feature(rr, rr.objects.railroads).features) | |
.enter().append("path") | |
.attr("d", path); | |
g.append("g") | |
.attr("class", "feature feature--station") | |
.selectAll("path") | |
.data(topojson.feature(rr, rr.objects.stations).features) | |
.enter().append("path") | |
.attr("d", path) | |
.on("click", clicked); | |
svg.call(zoom.scale(json_scale).event); | |
var train_json_url = ("https://www.googleapis.com/mapsengine/v1/tables/" + | |
"01382379791355219452-08584582962951999356" + | |
"/features?&version=published&maxResults=1000&key=" + | |
"AIzaSyB8k_VTTFxP75_a-na8jrY1Fk8oQClQMt8" + | |
"&maxResults=250"); | |
if (document.location.hostname == "localhost") { | |
//train_json_url = "./amtrak-sample.json"; | |
} | |
d3.json(train_json_url, function(error, train_json) { | |
train_json.features.forEach(function(t) { | |
t.properties.proj = projection(t.geometry.coordinates); | |
}); | |
console.log(train_json); | |
g.append("g") | |
.attr("class", "feature feature--train") | |
.selectAll("path") | |
.data(train_json.features) | |
.enter().append("path") | |
.attr("d", trainPath); | |
voronoi(train_json.features) | |
.forEach(function(d) {d.point.cell = d;}); | |
g.append("g") | |
.attr("class", "voronoi") | |
.selectAll("path") | |
.data(train_json.features) | |
.enter().append("path") | |
.attr("class", "train-cell") | |
.attr("d", function(d) {return d.cell.length ? "M" + d.cell.join("L") + "Z" : null;}) | |
.on("mouseover", function(d) { | |
trainLabel(d); | |
d3.event.preventDefault(); | |
}); | |
}); | |
}); | |
function clicked(d) { | |
if (activeState.node() === this) return reset(); | |
activeState.classed("active-state", false); | |
activeState = d3.select(this).classed("active-state", true); | |
var bounds = path.bounds(d), | |
dx = bounds[1][0] - bounds[0][0], | |
dy = bounds[1][1] - bounds[0][1], | |
x = (bounds[0][0] + bounds[1][0]) / 2, | |
y = (bounds[0][1] + bounds[1][1]) / 2, | |
scale = Math.min(.9 / Math.max(dx / width, dy / height), zoom.scaleExtent()[1]), | |
translate = [width / 2 - scale * x, height / 2 - scale * y]; | |
svg.transition() | |
.duration(750) | |
.call(zoom.translate(translate).scale(scale).event); | |
} | |
function reset() { | |
activeState.classed("active-state", false); | |
activeState = d3.select(null); | |
activeTrain = null; | |
d3.select('.label').remove(); | |
svg.transition() | |
.duration(750) | |
.call(zoom.translate([0, 0]).scale(.5).event); | |
} | |
function zoomed() { | |
translate = d3.event.translate; | |
scale = d3.event.scale; | |
g.style("stroke-width", 1.0 / d3.event.scale + "px"); | |
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); | |
path.pointRadius(4 / (d3.event.scale + .4)); | |
g.selectAll("g.feature--station path").attr("d", path); | |
g.selectAll("g.boundary--states").style("stroke-width", .8 / d3.event.scale + "px"); | |
trainPath.pointRadius(6 / (d3.event.scale + .4)); | |
g.selectAll("g.feature--train path").attr("d", trainPath); | |
if (activeTrain) {trainLabel(activeTrain);} | |
} | |
function stopped() { | |
if (d3.event.defaultPrevented) d3.event.stopPropagation(); | |
} | |
var format = d3.time.format("%Y-%m-%d %I:%M%p"); | |
function trainLabel(d) { | |
activeTrain = d; | |
var x = (d.properties.proj[0] * scale + translate[0]), | |
y = (d.properties.proj[1] * scale + translate[1]); | |
var label = d3.select('body').selectAll('div.label') | |
.data([d]) | |
var new_label = label.enter().append('div') | |
.attr('class', 'label') | |
.style('position', 'absolute'); | |
new_label.append('span').attr('class', 'trainnum'); | |
new_label.append('span').attr('class', 'routename'); | |
new_label.append('br'); | |
new_label.append('div').attr('class', 'origcode'); | |
new_label.append('div').attr('class', 'destcode'); | |
new_label.append('div').attr('class', 'velocity'); | |
new_label.append('div').attr('class', 'lastvalts'); | |
label | |
.transition().duration(100) | |
.style('left', (x + 10) + 'px') | |
.style('top', y + 'px') | |
.style('background-color', 'rgba(250, 250, 250, 0.5)') | |
.style('border-radius', '5px') | |
.style('padding', '3px') | |
.style('margin', '8px'); | |
label.selectAll('span.trainnum') | |
.text(d.properties.TrainNum + ' - '); | |
label.selectAll('span.routename') | |
.text(d.properties.RouteName); | |
label.selectAll('div.origcode') | |
.text("Origin: " + d.properties.OrigCode); | |
label.selectAll('div.destcode') | |
.text("Destination: " + d.properties.DestCode); | |
label.selectAll('div.velocity') | |
.text("Velocity: " + (+d.properties.Velocity).toFixed(1) + "mph"); | |
label.selectAll('div.lastvalts') | |
.text("Last Update: " + format(new Date(d.properties.LastValTS))); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment