Scuba tank size and buoyancy calculator, hosted at https://henrik.synth.no/scuba/tanks.html
Last active
March 30, 2022 10:19
-
-
Save henrik242/f0c5879b1ea74d00764b506ac90fe86d to your computer and use it in GitHub Desktop.
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
<html lang="en"> | |
<head> | |
<title>Scuba tank size and buoyancy calculator</title> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<script type="text/javascript"> | |
"use strict"; | |
let toDec = function (num, fourDigits) { | |
if (isNaN(num) || !isFinite(num)) return 0.0; | |
if (fourDigits) return parseFloat(num.toFixed(4)); | |
if (num.toFixed(1).slice(-1) === "0") return parseFloat(num.toFixed(0)); | |
return parseFloat(num.toFixed(1)); | |
}; | |
const BAR_PER_PSI = 0.06895; | |
const PSI_PER_ATM = 14.6959; | |
const LBS_PER_KG = 2.20462; | |
const LITERS_PER_CUFT = 28.31685; | |
const KG_LITER_IN_LBS_CUFT = LBS_PER_KG * LITERS_PER_CUFT; | |
const STEEL_DENSITY = 7.9; // kg/liter | |
const STEEL_DENSITY_IMP = toDec(STEEL_DENSITY * KG_LITER_IN_LBS_CUFT, true); // lbs/cuft | |
const ALU_DENSITY = 2.699; // kg/liter | |
const ALU_DENSITY_IMP = toDec(ALU_DENSITY * KG_LITER_IN_LBS_CUFT, true); // lbs/cuft | |
const AIR_DENSITY = 0.001225; // kg/liter | |
const AIR_DENSITY_IMP = toDec(AIR_DENSITY * KG_LITER_IN_LBS_CUFT, true); // lbs/cuft | |
const SALT_DENSITY = 1.024; // kg/liter | |
const SALT_DENSITY_IMP = toDec(SALT_DENSITY * KG_LITER_IN_LBS_CUFT, true); // lbs/cuft | |
const FRESH_DENSITY = 1.0; // kg/liter | |
const FRESH_DENSITY_IMP = toDec(FRESH_DENSITY * KG_LITER_IN_LBS_CUFT, true); // lbs/cuft | |
const VALVE_WEIGHT = 0.8; // kg | |
const METRIC = "metric"; | |
const IMPERIAL = "imperial"; | |
let lastunit = METRIC; | |
let elem = function (id) { | |
return document.getElementById(id); | |
}; | |
let valid = function (val) { | |
return !(val.value.slice(-1) === "\." || val.value.slice(-1) === "," || val.value.length === 0); | |
}; | |
let parse = function (elem) { | |
let parsed = parseFloat(elem.value.replace(",", ".").replace(/[^0-9.]/, "")); | |
if (isNaN(parsed) || parsed.length === 0) return 0; | |
return parsed; | |
}; | |
let setval = function (elem, val) { | |
if (elem.value !== toDec(val)) { | |
elem.value = toDec(val); | |
} | |
}; | |
let update = function (unit, message) { | |
if (unit) { | |
lastunit = unit; | |
} else { | |
unit = lastunit; | |
} | |
if (message) { | |
elem("message").style.display = "block"; | |
} else { | |
elem("message").style.display = "none"; | |
elem("predefined").value = ""; | |
} | |
let cuftElem = elem("cuft"); | |
let psiElem = elem("psi"); | |
let barElem = elem("bar"); | |
let litersElem = elem("liters"); | |
let kgElem = elem("kg"); | |
let lbsElem = elem("lbs"); | |
if (!(valid(cuftElem) && valid(psiElem) && valid(barElem) && valid(litersElem) && valid(kgElem) && valid(lbsElem))) { | |
return; | |
} | |
let cuft = parse(cuftElem); | |
let psi = parse(psiElem); | |
let bar = parse(barElem); | |
let liters = parse(litersElem); | |
let kg = parse(kgElem); | |
let lbs = parse(lbsElem); | |
if (unit === METRIC) { | |
cuft = (liters / LITERS_PER_CUFT) * bar; | |
psi = bar / BAR_PER_PSI; | |
lbs = kg * LBS_PER_KG; | |
} else { | |
liters = (cuft / (psi * BAR_PER_PSI)) * LITERS_PER_CUFT; | |
bar = psi * BAR_PER_PSI; | |
kg = lbs / LBS_PER_KG; | |
} | |
setval(cuftElem, cuft); | |
setval(psiElem, psi); | |
setval(barElem, bar); | |
setval(litersElem, liters); | |
setval(kgElem, kg); | |
setval(lbsElem, lbs); | |
if (unit === METRIC) { | |
window.location.hash = subst("#kg={0}&liters={1}&bar={2}&salt={3}&alu={4}&valve={5}&doubles={6}", [ | |
kg, | |
liters, | |
bar, | |
elem("salt").checked, | |
elem("alu").checked, | |
elem("valve").checked, | |
elem("doubles").checked, | |
]); | |
buoyancyMetric(kg, liters, bar); | |
} else { | |
window.location.hash = subst("#lbs={0}&cuft={1}&psi={2}&salt={3}&alu={4}&valve={5}&doubles={6}", [ | |
lbs, | |
cuft, | |
psi, | |
elem("salt").checked, | |
elem("alu").checked, | |
elem("valve").checked, | |
elem("doubles").checked, | |
]); | |
buoyancyImperial(lbs, cuft, psi); | |
} | |
}; | |
let buoyancyMetric = function (kg, liters, bar) { | |
let metal = elem("alu").checked ? ALU_DENSITY : STEEL_DENSITY; | |
let water = elem("salt").checked ? SALT_DENSITY : FRESH_DENSITY; | |
let valve = elem("valve").checked ? VALVE_WEIGHT : 0; | |
if (elem("doubles").checked) { | |
valve = valve * 2; | |
liters = liters * 2; | |
kg = kg * 2; | |
} | |
let volMetal = kg / metal; | |
let volValve = valve / STEEL_DENSITY; | |
let volume = (volMetal + volValve + liters) * water; | |
let air = AIR_DENSITY * bar * liters; | |
let empty = volume - kg - valve; | |
let full = volume - kg - valve - air; | |
elem("fullempty").value = toDec(empty) + "/" + toDec(full); | |
elem("fullemptylbs").value = toDec(empty * LBS_PER_KG) + "/" + toDec(full * LBS_PER_KG); | |
let txt = subst("<br><br>Steel has a density of {0} kg/liter", [STEEL_DENSITY, true]); | |
if (metal !== STEEL_DENSITY) { | |
txt += subst(", and aluminium is {0} kg/liter", [ALU_DENSITY]); | |
} | |
txt += subst("<br>The volume of the tank metal is {0} kg / {1} = <b>{2} liters</b><br>", [kg, metal, toDec(volMetal, 1)]); | |
let plusValve = ""; | |
let minusValve = ""; | |
if (valve !== 0) { | |
txt += subst("The volume of the valve is {0} kg / {1} = <b>{2} liters</b><br>", [ | |
toDec(valve, 1), | |
STEEL_DENSITY, | |
toDec(volValve, 1), | |
]); | |
plusValve = " + " + toDec(volValve, 1); | |
minusValve = " - " + toDec(valve, 1); | |
} | |
if (water === SALT_DENSITY) { | |
txt += subst("The density of salt water is {0} kg/liter<br>", [SALT_DENSITY]); | |
} else { | |
txt += subst("The density of fresh water is {0} kg/liter<br>", [FRESH_DENSITY]); | |
} | |
txt += | |
subst("Total weight in water: ({0} + {1} {2}) x {3} = <b>{4} kg</b><br>", [ | |
liters, | |
toDec(volMetal, 1), | |
plusValve, | |
water, | |
toDec(volume, 1), | |
]) + | |
subst("Air has a density of {0} kg/liter. <br>The air in a full tank weighs {1} x {2} liters x {3} bar = <b>{4} kg</b><br>", [ | |
AIR_DENSITY, | |
AIR_DENSITY, | |
liters, | |
bar, | |
toDec(air, 1), | |
]) + | |
subst("Tank buoyancy when empty: {0} - {1} {2} = <b>{3} kg</b><br>", [toDec(volume, 1), kg, minusValve, toDec(empty)]) + | |
subst("Tank buoyancy when full: {0} - {1} {2} - {3} = <b>{4} kg</b><br>", [ | |
toDec(volume, 1), | |
kg, | |
minusValve, | |
toDec(air, 1), | |
toDec(full), | |
]); | |
elem("calculation").innerHTML = txt; | |
}; | |
let buoyancyImperial = function (lbs, cuft, psi) { | |
let metal = elem("alu").checked ? ALU_DENSITY_IMP : STEEL_DENSITY_IMP; | |
let water = elem("salt").checked ? SALT_DENSITY_IMP : FRESH_DENSITY_IMP; | |
let valve = elem("valve").checked ? VALVE_WEIGHT * LBS_PER_KG : 0; | |
if (elem("doubles").checked) { | |
valve = valve * 2; | |
cuft = cuft * 2; | |
lbs = lbs * 2; | |
} | |
let volInner = (cuft / psi) * PSI_PER_ATM; | |
let volMetal = lbs / metal; | |
let volValve = valve / STEEL_DENSITY_IMP; | |
let volume = (volInner + volMetal + volValve) * water; | |
let air = AIR_DENSITY_IMP * cuft; | |
let empty = volume - lbs - valve; | |
let full = volume - lbs - valve - air; | |
elem("fullempty").value = toDec(empty / LBS_PER_KG) + "/" + toDec(full / LBS_PER_KG); | |
elem("fullemptylbs").value = toDec(empty) + "/" + toDec(full); | |
let txt = | |
subst("<br><br>Air has a pressure of {0} psi at 1 ATM.<br> Tank inner volume is {1} cuft / {2} psi x {3} = <b>{4} cuft</b><br>", [ | |
PSI_PER_ATM, | |
cuft, | |
psi, | |
PSI_PER_ATM, | |
toDec(volInner, 1), | |
]) + subst("Steel has a density of {0} lbs/cuft", [STEEL_DENSITY_IMP]); | |
if (metal !== STEEL_DENSITY_IMP) { | |
txt += subst(", and aluminium is {0} lbs/cuft", [ALU_DENSITY_IMP]); | |
} | |
txt += subst("<br>The volume of the tank metal is {0} lbs / {1} = <b>{2} cuft</b><br>", [lbs, metal, toDec(volMetal, 1)]); | |
let plusValve = ""; | |
let minusValve = ""; | |
if (valve !== 0) { | |
txt += subst("The volume of the valve is {0} lbs / {1} = <b>{2} cuft</b><br>", [ | |
toDec(valve, 1), | |
STEEL_DENSITY_IMP, | |
toDec(volValve, 1), | |
]); | |
plusValve = " + " + toDec(volValve, 1); | |
minusValve = " - " + toDec(valve, 1); | |
} | |
if (water === SALT_DENSITY_IMP) { | |
txt += subst("The density of salt water is {0} lbs/cuft<br>", [SALT_DENSITY_IMP]); | |
} else { | |
txt += subst("The density of fresh water is {0} lbs/cuft<br>", [FRESH_DENSITY_IMP]); | |
} | |
txt += | |
subst("Total weight in water: ({0} + {1} {2}) x {3} = <b>{4} lbs</b><br>", [ | |
toDec(volInner, 1), | |
toDec(volMetal, 1), | |
plusValve, | |
water, | |
toDec(volume, 1), | |
]) + | |
subst("Air has a density of {0} lbs/cuft. <br>The air in a full tank weighs {1} x {2} cuft = <b>{3} lbs</b><br>", [ | |
AIR_DENSITY_IMP, | |
AIR_DENSITY_IMP, | |
cuft, | |
toDec(air, 1), | |
]) + | |
subst("Tank buoyancy when empty: {0} - {1} {2} = <b>{3} lbs</b><br>", [toDec(volume, 1), lbs, minusValve, toDec(empty)]) + | |
subst("Tank buoyancy when full: {0} - {1} {2} - {3} = <b>{4} lbs</b><br>", [ | |
toDec(volume, 1), | |
lbs, | |
minusValve, | |
toDec(air, 1), | |
toDec(full), | |
]); | |
elem("calculation").innerHTML = txt; | |
}; | |
let subst = function (str, arr) { | |
let i, | |
pattern, | |
re, | |
n = arr.length; | |
for (i = 0; i < n; i++) { | |
pattern = "\\{" + i + "\\}"; | |
re = new RegExp(pattern, "g"); | |
str = str.replace(re, arr[i]); | |
} | |
return str; | |
}; | |
let showHide = function () { | |
if (elem("calculation").style.display === "inline") { | |
elem("calculation").style.display = "none"; | |
elem("showhide").innerHTML = "Show calculation"; | |
} else { | |
elem("calculation").style.display = "inline"; | |
elem("showhide").innerHTML = "Hide calculation"; | |
} | |
elem("showhide").href = window.location.href; | |
}; | |
let showHelp = function () { | |
if (elem("help").style.display === "inline") { | |
elem("help").style.display = "none"; | |
} else { | |
elem("help").style.display = "inline"; | |
} | |
}; | |
let metric = function (liters, bar, kg, alu, doubles) { | |
elem("bar").value = bar; | |
elem("liters").value = liters; | |
elem("kg").value = kg; | |
elem("alu").checked = alu === "1"; | |
elem("doubles").checked = doubles === "1"; | |
update(METRIC, liters !== "0" || bar !== "0" || kg !== "0" || alu !== "0" || doubles !== "0"); | |
}; | |
let imperial = function (cuft, psi, lbs, alu, doubles) { | |
elem("cuft").value = cuft; | |
elem("psi").value = psi; | |
elem("lbs").value = lbs; | |
elem("alu").checked = alu === "1"; | |
elem("doubles").checked = doubles === "1"; | |
update(IMPERIAL, cuft !== "0" || psi !== "0" || lbs !== "0" || alu !== "0" || doubles !== "0"); | |
}; | |
let selectTank = function () { | |
let p = elem("predefined").value.split(";"); | |
switch (p[0]) { | |
case METRIC: | |
return metric(p[1], p[2], p[3], p[4], p[5]); | |
case IMPERIAL: | |
return imperial(p[1], p[2], p[3], p[4], p[5]); | |
default: | |
update(); | |
} | |
}; | |
let parseHash = function (unit) { | |
let parsed = window.location.hash.match(new RegExp(unit + "=([0-9.]+)")); | |
if (parsed && parsed.length > 0 && !isNaN(parsed[1]) && isFinite(parsed[1])) { | |
elem(unit).value = parsed[1]; | |
} | |
}; | |
let parseHashCheckbox = function (unit) { | |
let parsed = window.location.hash.match(new RegExp(unit + "=([a-z]+)")); | |
if (parsed && parsed.length > 0) { | |
elem(unit).checked = parsed[1] === "true"; | |
} | |
}; | |
window.onload = function () { | |
parseHashCheckbox("salt"); | |
parseHashCheckbox("alu"); | |
parseHashCheckbox("valve"); | |
parseHashCheckbox("doubles"); | |
if (window.location.hash.match(/lbs/)) { | |
parseHash("lbs"); | |
parseHash("psi"); | |
parseHash("cuft"); | |
update(IMPERIAL); | |
} else { | |
parseHash("kg"); | |
parseHash("bar"); | |
parseHash("liters"); | |
update(METRIC); | |
} | |
}; | |
</script> | |
<style> | |
body { | |
font-family: Arial, Helvetica, sans-serif; | |
} | |
span, | |
th { | |
white-space: nowrap; | |
} | |
input, | |
td { | |
text-align: center; | |
font-size: 1.2em; | |
border-radius: 5px; | |
} | |
select { | |
background-color: #eee; | |
font-size: 1em; | |
font-weight: normal; | |
} | |
th, | |
.small { | |
font-size: 0.8em; | |
font-weight: normal; | |
} | |
table { | |
width: 400px; | |
} | |
b { | |
color: blue; | |
font-weight: normal; | |
} | |
#calculation, | |
#message { | |
display: none; | |
} | |
#message { | |
font-size: 0.7em; | |
color: red; | |
padding-bottom: 10px; | |
} | |
#fullempty, | |
#fullemptylbs { | |
border: none; | |
font-size: 1.1em; | |
} | |
.check { | |
background-color: #eee; | |
padding: 1px 6px 2px 2px; | |
margin: 2px; | |
font-size: 0.8em; | |
font-weight: normal; | |
border-radius: 5px; | |
} | |
#checkboxes { | |
margin: 10px 1px 15px 1px; | |
} | |
.circle { | |
border: 0.1em solid grey; | |
border-radius: 100%; | |
height: 1.8em; | |
width: 1.8em; | |
text-align: center; | |
} | |
.circle p { | |
margin-top: 0.1em; | |
font-size: 1.3em; | |
font-weight: bold; | |
font-family: sans-serif; | |
color: grey; | |
} | |
#help { | |
display: none; | |
} | |
.bottom { | |
display: flex; | |
align-items: center; | |
width: 100%; | |
margin-top: 10px; | |
margin-bottom: 20px; | |
} | |
.expand { | |
flex: 1; | |
} | |
#predefined { | |
margin-bottom: 10px; | |
} | |
</style> | |
</head> | |
<body> | |
<form> | |
<table> | |
<tr> | |
<td colspan="4">Scuba tank size and buoyancy calculator</td> | |
</tr> | |
<tr> | |
<td><br /></td> | |
</tr> | |
<tr> | |
<th title="Tank size in liters">liters</th> | |
<th title="Tank pressure in bar">bar</th> | |
<th title="Tank weight in kilograms">kg</th> | |
<th title="Tank buoyancy in kilograms">empty/full kg</th> | |
</tr> | |
<tr> | |
<td><input size="5" class="right" id="liters" onKeyUp="update('metric')" value="0" title="Tank size in liters" /></td> | |
<td><input size="6" class="right" id="bar" onKeyUp="update('metric')" value="0" title="Tank pressure in bar" /></td> | |
<td><input size="4" class="right" id="kg" onKeyUp="update('metric')" value="0" title="Tank weight in kilograms" /></td> | |
<td><input size="10" id="fullempty" readonly title="Tank buoyancy in kilograms" value="0/0" /></td> | |
</tr> | |
<tr> | |
<td><input size="5" class="right" id="cuft" onKeyUp="update('imperial')" value="0" title="Tank size in cubic feet" /></td> | |
<td><input size="6" class="right" id="psi" onKeyUp="update('imperial')" value="0" title="Tank pressure in psi" /></td> | |
<td><input size="4" class="right" id="lbs" onKeyUp="update('imperial')" value="0" title="Tank weight in pounds" /></td> | |
<td><input size="10" id="fullemptylbs" readonly title="Tank buoyancy in pounds" value="0/0" /></td> | |
</tr> | |
<tr> | |
<th title="Tank size in cubic feet">cuft</th> | |
<th title="Tank pressure in psi">psi</th> | |
<th title="Tank weight in lbs">lbs</th> | |
<th title="Tank buoyancy in pounds">empty/full lbs</th> | |
</tr> | |
<tr> | |
<td colspan="4"> | |
<div id="checkboxes"> | |
<span title="Diving in salt or fresh water" class="check" | |
><input id="salt" onchange="update()" type="checkbox" checked /><label for="salt"> salt</label></span | |
> | |
<span title="Double tank" class="check" | |
><input id="doubles" onchange="update()" type="checkbox" /><label for="doubles"> doubles</label></span | |
> | |
<span title="Aluminium or steel tank" class="check" | |
><input id="alu" onchange="update()" type="checkbox" /><label for="alu"> alu</label></span | |
> | |
<span title="Calculation with valve weight" class="check" | |
><input id="valve" onchange="update()" type="checkbox" checked /><label for="valve"> valve</label></span | |
> | |
</div> | |
<div id="message"> | |
This predefined tank might be different from your tank! Check the neck of your tank for real weight and capacity. | |
</div> | |
<label> | |
<select id="predefined" onchange="selectTank()"> | |
<option value="">select predefined tank</option> | |
<option value="metric;0;0;0;0;0">reset tank values</option> | |
<option value="imperial;77.4;3000;32;1;0">AL80</option> | |
<option value="imperial;80;2640;26.5;0;0">LP80</option> | |
<option value="imperial;80;3442;30;0;0">HP80</option> | |
<option value="imperial;104;2640;46.4;0;0">LP104</option> | |
<option value="imperial;100;3442;33;0;0">HP100</option> | |
<option value="metric;10;300;15.4;0;0">10L / 300 bar</option> | |
<option value="metric;12;200;14;0;0">12L / 200 bar</option> | |
<option value="metric;15;232;16;0;0">15L / 232 bar</option> | |
<option value="metric;7;300;10.7;0;1">D7 / 300 bar</option> | |
<option value="metric;8.5;232;10.3;0;1">D8.5 / 232 bar</option> | |
<option value="metric;12;232;13.8;0;1">D12 / 232 bar</option> | |
</select> | |
</label> | |
</td> | |
</tr> | |
<tr> | |
<td colspan="4" class="small"> | |
<a id="showhide" onclick="showHide()" href="#">Show calculation</a> | |
<div id="calculation"></div> | |
<div class="bottom"> | |
<div title="Source code available at GitHub"> | |
<a href="https://gist.github.com/henrik242/f0c5879b1ea74d00764b506ac90fe86d"> | |
<img alt="" src="https://github.githubassets.com/images/modules/site/icons/footer/github-mark.svg" /> | |
</a> | |
</div> | |
<div class="expand"></div> | |
<div title="This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License"> | |
<a rel="license" href="https://creativecommons.org/licenses/by-sa/3.0/"> | |
<img | |
alt="This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License" | |
style="border-width: 0" | |
src="https://i.creativecommons.org/l/by-sa/3.0/80x15.png" | |
/> | |
</a> | |
</div> | |
<div class="expand"></div> | |
<div class="circle" onclick="showHelp()" title="Show help"> | |
<p>?</p> | |
</div> | |
</div> | |
<p id="help"> | |
This calculator converts between metric and imperial values, and calculates tank buoyancy. Type the values manually or select | |
from the pre-defined list of common tanks. | |
</p> | |
</td> | |
</tr> | |
</table> | |
</form> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment