MUNI Greenhouse Gas Emissions (Realtime)
xxxxxxxxxx
<meta charset="utf-8">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<style>
html{
background-color: #000;
}
body {
width: 1024px;
margin-top: 0;
margin: auto;
font-family: "Lato", "PT Serif", serif;
color: #000;
padding: 0;
font-weight: 300;
line-height: 24px;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
.lines{
fill: none;
stroke:#95a5a6;
opacity: 0.3;
}
.vehPoint{
fill-opacity: 0.8;
stroke-width: 2px;
}
.key path {
display: none;
}
.key line {
stroke: #fff;
shape-rendering: crispEdges;
}
.legend-title {
font-weight: bold;
}
.legend-box {
fill: none;
stroke: #888;
font-size: 10px;
}
#title{
position: absolute;;
top: 10px;
left: 20px;
}
#basemap{
position: absolute;;
top: 80px;
left: 20px;
}
#legend{
position: fixed;
top: 85px;
left: 20px;
z-index: 100;
}
#more-info{
position: fixed;
bottom: 15px;
right: 30px;
z-index: 100;
}
#more-info:hover{
opacity: 0.8;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<body>
<div>
<div id='title'><h1 style='color:#fff'>SF MUNI bus greenhouse gas emissions in <em><u>realtime</u></em>.</h1></div>
<div id='basemap'></div>
<div id='legend'></div>
<div id='more-info'><button type="button" class="btn btn-xs" data-toggle="modal" data-target="#myModal"><img src='question.png'></button></div>
<!-- Modal -->
<div id="myModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Methods</h4>
</div>
<div class="modal-body">
<p> The greenhouse gas emission rates for MUNI buses take into account all emissions from
the production and distribution of the fuels as well as any the direct tailpipe emissions.
For electric-powered buses, which sources its power from the Hetch Hetchy reservoir, we
assign an carbon-intensity of 4 kWh/km.</p>
<p><strong>Sources:<strong></p>
<ul>
<li><a href='https://www.arb.ca.gov/msei/categories.htm' target='_blank'>California Air Resources Board EMFAC2014 model</a></li>
<li><a href='https://greet.es.anl.gov/' target='_blank'>Argonne National Laboratory GREET model</a></li>
<li><a href='https://www.nextbus.com/' target='_blank'>NextBus, Inc. API</a></li>
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<body>
<script>
(function(){
var width = 1500,
height = 1250,
TRANSITION_DELAY = 3000,
formatNumber = d3.format("0,000");
var muniColors = {
'F': "none",
'J': "none",
'K': "none",
'L': "none",
'M': "none",
'N': "none",
'T': "none"
};
var radius = d3.scale.quantile()
.domain([1510,4450])
.range(d3.range(9).map(function(i) { return i }));
var rateById = d3.map();
var color = d3.scale.quantile()
.domain([0,4450])
.range(['#a50026','#d73027','#f46d43','#fdae61','#fee08b','#ffffbf','#d9ef8b','#a6d96a','#66bd63','#1a9850','#006837'].reverse());
var projection = d3.geo.mercator()
.center([-122.438701, 37.758])
.scale(520000)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#basemap").append("svg")
.attr("width", width)
.attr("height", height);
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.style("color", "#000")
.style("padding", "8px")
.style("background-color", "rgba(256, 256, 256, 0.85)")
.style("border-radius", "6px")
.style("font", "18px sans-serif")
.html("tooltip");
queue()
.defer(d3.json, "sfmuni.json")
.await(ready);
function ready(error, sf) {
if (error) throw error;
svg.append("g")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(sf, sf.objects.sfmuni).features)
.enter().append("path")
.attr('class', 'lines')
.attr("d", path);
d3.xml('https://webservices.nextbus.com/service/publicXMLFeed?command=vehicleLocations&a=sf-muni&t=0', drawmuni);
// Reload muni data every 15s
setInterval(function() {
d3.xml('https://webservices.nextbus.com/service/publicXMLFeed?command=vehicleLocations&a=sf-muni&t=0', drawmuni);
}, 3000);
}
// Emission Models
var v2e = function (x) {
if(x<=0) return {"e":4445, "dv":4445}
var avg = 2120;
var arr = [8,16,24,32,40,48,56,64,72,80,88,97,105,113]
var orr = [4445,3890,2895,2195,1930,1790,1685,1595,1510,1450,1425,1430,1440,1510]
var max = function (arrr) { return Math.max.apply(null, arrr); };
var min = function (arrr) { return Math.min.apply(null, arrr); };
var l = [], h = [];
arr.forEach(function (v) {
((v < x) && l.push(v)) || ((v > x) && h.push(v));
});
var H = arr.indexOf(min(h))
var L = arr.indexOf(max(l))
if(L<0) return {"e":orr[0], "dv":(orr[0]-avg)/avg}
if(H<0) return {"e": orr[orr.length-1], "dv":(orr[orr.length-1] - avg)/avg}
else{
var e = (x - arr[L]) / (arr[H] - arr[L]) * (orr[H] - orr[L]) + orr[L];
return {"e":parseInt(e), "dv":(e-avg)/avg}
}
};
var is_electric = function(x){
var yes_i_am_electric = ["1","14","21","22","24","3","30","33", "41","45","5", "59","6", "60", "61"];
return yes_i_am_electric.indexOf(x) > -1
}
function vehid(d) {
return d.getAttribute("id");
}
function drawmuni(munidata) {
var translation = function(d) {
var pt = projection([d.getAttribute("lon"), d.getAttribute("lat")]);
return "translate(" + pt[0] + "," + pt[1] + ")" +
"rotate(" + d.getAttribute("heading") + ")" +
"scale(0.9)";
};
vehicles = munidata.getElementsByTagName('vehicle');
vehPoints = svg.selectAll(".vehPoint")
.data(vehicles, vehid)
.on("mouseover", function(d) {
tooltip.html( "<strong>Route:</strong> <span style='color:red'>" + d.getAttribute("routeTag") + "</span><br><span style='color:red; font-size:14px;'>" + formatNumber(Math.round(v2e(+d.getAttribute("speedKmHr")).e /100)*100) + "</span><strong style='font-size:10px;'> g CO<sub>2,e</sub> / km</strong>");
tooltip.style("visibility", "visible");
})
.on("mousemove", function() {
return tooltip.style("top", (d3.event.pageY-70)+"px").style("left",(d3.event.pageX-55)+"px");
})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
var triangle = d3.svg.symbol().type("triangle-up");
vehPoints.transition().duration(TRANSITION_DELAY)
.attr("fill", function(d) {
var tag = d.getAttribute("routeTag");
if(is_electric(tag)){ return color(4)} // Electricity sourced From Hetch Hetchy hydro
else{return muniColors[tag] || color(v2e(+d.getAttribute("speedKmHr")).e)}
})
.attr("transform", translation)
vehPoints.enter()
.append("svg:path")
.attr("class", "vehPoint")
.attr("transform", translation)
.attr("d", triangle)
.attr("fill", function(d) {
var tag = d.getAttribute("routeTag");
if(is_electric(tag)){ return color(4)} // Electricity sourced From Hetch Hetchy hydro
else{return muniColors[tag] || color(v2e(+d.getAttribute("speedKmHr")).e)}
})
.attr("opacity", 0.0)
.transition().duration(TRANSITION_DELAY)
.attr("opacity", 0.9);
vehPoints.exit().transition().duration(TRANSITION_DELAY)
.attr("opacity", 0.0)
.remove();
}
d3.select(self.frameElement).style("height", height + "px");
})()
</script>
<script>
(function(){
var svg = d3.select("#legend").append("svg")
.attr("width", 140)
.attr("height", 290)
.style('background', '#000');
var boxmargin = 10,
lineheight = 18,
keyheight = 15,
keywidth = 60,
boxwidth = 2 * keywidth,
formatNumber = d3.format("0,000"),
margin = { "left": 10, "top": 10 };
var color = d3.scale.quantile()
.domain([0,4450])
.range(['#a50026','#d73027','#f46d43','#fdae61','#fee08b','#ffffbf','#d9ef8b','#a6d96a','#66bd63','#1a9850','#006837'].reverse());
var ranges = color.range().length;
var title = ['MUNI Bus','g CO2,e / km'],
titleheight = title.length*lineheight + boxmargin;
var legend = svg.append("g")
.attr("transform", "translate ("+margin.left+","+margin.top+")")
.attr("class", "legend");
legend.selectAll("text")
.data(title)
.enter().append("text")
.attr("class", "legend-title")
.attr('x', boxwidth/2)
.attr("y", function(d, i) { return (i+1)*lineheight*1.4-15; })
.html(function(d) { return d; })
.style('fill', '#eee')
.style('text-anchor', 'middle')
.style('font-size', '24')
// make legend box
var lb = legend.append("rect")
.attr("transform", "translate (0,"+titleheight+")")
.attr("class", "legend-box")
.attr("width", boxwidth)
.attr("height", ranges*lineheight+2*boxmargin+lineheight-keyheight);
// make quantized key legend items
var li = legend.append("g")
.attr("transform", "translate (8,"+(titleheight+boxmargin)+")")
.attr("class", "legend-items");
li.selectAll("rect")
.data(d3.range(ranges).map(function(d){return (ranges-d-1)*500}))
.enter().append("rect")
.attr("y", function(d, i) { return i*lineheight+lineheight-keyheight; })
.attr("width", keywidth)
.attr("height", keyheight)
.style("fill", function(d) { return color(d); });
li.selectAll("text")
.data(d3.range(ranges).map(function(d){return (ranges-d-1)*500}))
.enter().append("text")
.attr("x", keywidth+5)
.attr("y", function(d, i) { return (i+1)*lineheight + 5; })
.text(function(d) { return formatNumber(Math.round(d/10)*10); })
.style('fill', '#eee');
})()
</script>
Modified http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js to a secure url
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js
https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js
https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js
https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js
https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js