Skip to content

Instantly share code, notes, and snippets.

@henrik242
Last active March 30, 2022 10:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save henrik242/f0c5879b1ea74d00764b506ac90fe86d to your computer and use it in GitHub Desktop.
Save henrik242/f0c5879b1ea74d00764b506ac90fe86d to your computer and use it in GitHub Desktop.
<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