The control of contaminant migration in groundwater is a common task for environmental engineers. One way of completing this work is by installing recovery wells downstream of the chemical plume. In this visualization, I illustrate how pumping rates effect the "capture zone" of the recovery well under various groundwater flow rates. The setting assumes that the plume is contained within a confined aquifer that is 30m thick. The coordinates are a plan view of the system and the groundwater flow direction is shown via the moving circles.
Last active
June 5, 2016 17:33
-
-
Save mtaptich/d339957ec9a7324de6413ca82f820b82 to your computer and use it in GitHub Desktop.
Capture the Plume!
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> | |
body { | |
width: 1024px; | |
margin-top: 0; | |
margin: auto; | |
font-family: "Lato", "PT Serif", serif; | |
color: #222222; | |
padding: 0; | |
font-weight: 300; | |
line-height: 28px; | |
-webkit-font-smoothing: antialiased; | |
} | |
.axis path, | |
.axis line{ | |
fill: none; | |
stroke: black; | |
} | |
.line{ | |
fill: none; | |
stroke: blue; | |
stroke-width: 2px; | |
} | |
.tick text{ | |
font-size: 12px; | |
} | |
.tick line{ | |
opacity: 0.2; | |
} | |
.xbottom path.domain{ | |
stroke:none; | |
} | |
.x text, .y text{ | |
fill:none; | |
} | |
.plume{ | |
fill:#9b59b6; | |
fill-opacity: 0.5; | |
stroke: #000; | |
stroke-width:2px; | |
} | |
.running{ | |
fill: #2980b9; | |
} | |
</style> | |
<body> | |
<div id='graph'></div> | |
<div style='width:500px; margin-right:50px;'> | |
<div style="float:left; width:200px; margin-left:50px"> | |
<div>GW Flow Rate (Ki):</div> | |
<label for="nflow"> | |
<span id="flow-value" style='font-weight:bold'>5</span> cm/s <br> | |
<input style="width: 120px;" type="range" min="1" max="10" id="flow" value="3.4" step='0.1'> | |
</label> | |
</div> | |
<div style="float:left; width:200px;"> | |
<div>Pumping Rate (Q):</div> | |
<label for="nQ"> | |
<span id="Q-value" style='font-weight:bold'>500</span> m<sup>3</sup>/day<br> | |
<input style="width: 120px;" type="range" min="50" max="2000" id="Q" value="500" step='1'> | |
</label> | |
</div> | |
</div> | |
<script src="//d3js.org/d3.v3.min.js"></script> | |
<script> | |
var margin = {top: 20, right: 220, bottom: 50, left: 100}, | |
width = 650 - margin.left - margin.right, | |
height = 400 - margin.top - margin.bottom, | |
flow = 5, | |
Q = 500/86400, | |
plume_width = 250; | |
var x = d3.scale.linear() | |
.range([0, width]) | |
.domain([-90*3,210*3]); | |
var y = d3.scale.linear() | |
.range([height, 0]) | |
.domain([-150*3,150*3]); | |
var color = d3.scale.category10(); | |
var xAxistop = d3.svg.axis() | |
.scale(x) | |
.orient("bottom") | |
.innerTickSize(-height/2) | |
.outerTickSize(0) | |
.tickPadding(10); | |
var xAxisbottom = d3.svg.axis() | |
.scale(x) | |
.orient("bottom") | |
.innerTickSize(height/2) | |
.outerTickSize(0) | |
.tickPadding(10); | |
var yAxisRight = d3.svg.axis() | |
.scale(y) | |
.orient("left") | |
.innerTickSize(-width*.7) | |
.outerTickSize(0) | |
.tickPadding(10); | |
var yAxisLeft = d3.svg.axis() | |
.scale(y) | |
.orient("left") | |
.innerTickSize(width*.3) | |
.outerTickSize(0) | |
.tickPadding(10); | |
var svg = d3.select("#graph").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
svg.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + height/2 + ")") | |
.call(xAxistop) | |
svg.append("g") | |
.attr("class", "xbottom axis") | |
.attr("transform", "translate(0," + height/2 + ")") | |
.call(xAxisbottom) | |
.append("text") | |
.attr("class", "label") | |
.attr("x", width) | |
.attr("y", height/2) | |
.attr('dy', "2.2em") | |
.style("text-anchor", "end") | |
.text("x (e.g., longitudinal axis), meters"); | |
svg.append("g") | |
.attr("class", "y axis") | |
.attr("transform", "translate(" + width*0.3 + ",0)") | |
.call(yAxisRight) | |
svg.append("g") | |
.attr("class", "yleft axis") | |
.attr("transform", "translate(" + width*0.3 + ",0)") | |
.call(yAxisLeft) | |
.append("text") | |
.attr("class", "label") | |
.attr("transform", "rotate(-90)translate(0," + -width*0.3 + ")") | |
.attr("y", -50) | |
.attr("dy", ".71em") | |
.style("text-anchor", "end") | |
.text("y, meters") | |
var area = svg.append('path') | |
.attr('class', 'plume') | |
svg.append('circle') | |
.attr('cx', x(0)) | |
.attr('cy', y(0)) | |
.attr('r', 3) | |
.style('fill','#fff') | |
.style('stroke','#000') | |
function input_data(){ | |
var xposition = 1300 + Math.random()*50*(flow/5); | |
var c = svg.append('circle') | |
.attr('cy', (y(-plume_width) - y(plume_width))*Math.random() + y(plume_width)) | |
.attr('cx', x(xposition)) | |
.attr('r', (3*Math.random()) + 2) | |
.attr('class', 'running') | |
.style('fill-opacity', Math.random() + 0.3) | |
c.transition() | |
.duration((xposition-600)/(flow/100)) | |
.attr('cx', x(600)) | |
.ease('linear') | |
.transition() | |
.duration(100) | |
.style('opacity', 0) | |
.remove(); | |
} | |
function boundary(y){ | |
return -y / Math.tan(2*Math.PI * (flow/(100*24*3600)) * 30 * y / Q) | |
} | |
function cone(){ | |
d = 'M'+x(boundary(0.1))+" "+y(0.1) | |
py = 1 | |
px = boundary(py) | |
while (px <= 600) { | |
px = boundary(py); | |
d+= ' L '+x(px)+" "+y(py)+',' | |
py+=0.1 | |
} | |
d+= 'M'+x(boundary(0.1))+" "+y(0.1) | |
py = 1 | |
px = boundary(py) | |
while (px <= 600 ) { | |
px = boundary(py); | |
d+= ' L '+x(px)+" "+y(-py)+',' | |
py+=0.1 | |
} | |
d+= ' L '+x(px)+" "+y(py) | |
area.attr('d', d) | |
} | |
d3.select("#flow").on("input", function() { | |
flow = +this.value; | |
d3.select('#flow-value').text(flow); | |
cone() | |
//cone() | |
}); | |
d3.select("#Q").on("input", function() { | |
var eQ = +this.value; | |
Q = eQ/86400 | |
d3.select('#Q-value').text(eQ); | |
cone() | |
//cone() | |
}); | |
function pulse(){ | |
for (var i = 0; i < 50; i++) { | |
input_data() | |
} | |
} | |
pulse() | |
setInterval(pulse, 1000) | |
setInterval(function(){ plume_width = Math.round(Math.random()*40)*10;}, 10000) | |
cone() | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment