Data Source and Methodology
Authoritative Data Source: US Army Corps of Engineers (USACE), Engineer Manual EM 1110-2-2300 — “Earth and Rock-Fill Dams: General Design and Construction Considerations,” July 2004. Direct PDF. Typical bulking and shrinkage practices for earthwork are documented within USACE earthwork guidance.
Additionally, the volumetric methodology used here aligns with standard civil engineering practice and GIS implementations such as Esri’s Cut/Fill Volume Calculation, which compute volumes by integrating height differences over area.
Statement: All calculations are rigorously based on the formulas and data provided by this source.
The Formula Explained
For each cell i with area A_i and adjusted height difference Δh_i between existing and design grades:
\( \Delta h_i = (E^{\text{existing}}_i - t) - E^{\text{design}}_i \)
\( V_{\text{cut}} = \sum_{i} \max(\Delta h_i, 0)\cdot A_i \quad,\quad V_{\text{fill}} = \sum_{i} \max(-\Delta h_i, 0)\cdot A_i \)
Adjustments for bulking (swell) and compaction (shrinkage):
\( V_{\text{cut, loose}} = V_{\text{cut}} \cdot (1 + s) \quad,\quad V_{\text{borrow, loose}} = \dfrac{V_{\text{fill}}}{1 - c} \)
Where t is topsoil strip thickness, s and c are decimals (e.g., 20% → 0.20).
Glossary of Variables
- Eexisting, Edesign: Existing and design elevations per area or grid cell (m or ft).
- A: Area of the site (Simple mode) or uniform cell area (Grid mode) in m² or ft².
- t: Topsoil strip thickness subtracted from existing elevation (m or ft).
- Δh: Height difference. Positive → cut; negative → fill.
- Vcut, Vfill: In-place volumes before bulking/compaction (m³ or yd³ as applicable).
- s: Bulking (swell) fraction, e.g., 0.20 for 20%.
- c: Compaction fraction, e.g., 0.10 for 10%.
- Vcut, loose: Loose excavated volume after swell (used for haul/export).
- Vborrow, loose: Loose borrow required to achieve compacted fill.
How It Works: A Step-by-Step Example
Scenario: Metric units. Area = 2,000 m². Average existing elevation = 101.2 m. Design elevation = 100.7 m. Topsoil strip t = 0.00 m. Swell s = 20% (0.20). Compaction c = 10% (0.10).
- Compute height difference: Δh = (101.2 − 0.00) − 100.7 = 0.5 m → Cut.
- In-place volumes: Vcut = 0.5 × 2,000 = 1,000 m³; Vfill = 0.
- Loose cut: Vcut, loose = 1,000 × (1 + 0.20) = 1,200 m³.
- Borrow: Vborrow, loose = 0 ÷ (1 − 0.10) = 0 m³.
- Balance: Export 1,200 m³ loose if not reused; import is 0.
Frequently Asked Questions (FAQ)
Do I need to enter negative numbers for fill?
No. Enter actual elevations. The calculator detects whether each cell/area is cut (existing above design) or fill (existing below design).
How accurate are results compared to 3D models?
For conceptual estimates, this 2D height-difference method is standard. For final takeoffs, validate against your project’s digital terrain models (DTMs) or BIM.
What values should I use for bulking and compaction?
They depend on soil type and moisture. Typical ranges: granular soils 10–25% swell; clays 15–40%. Always confirm with geotechnical data and specs.
Can I account for topsoil stripping?
Yes. Enter the strip thickness; the calculator subtracts it from existing elevations before computing volumes.
What unit conversions are shown?
Primary outputs follow your unit setting. A secondary line shows the same volumes converted between m³ and yd³.
How do I know if I need import or export?
If loose cut exceeds borrow required, you have export. If borrow required exceeds loose cut, you need import. The Balance line summarizes this.
Formula (LaTeX) + variables + units
= (sel, ctx=document) => Array.from(ctx.querySelectorAll(sel)); // Elements const modeRadios =
('input[name="units"]'); const simplePane = $('#simple-pane'); const gridPane = $('#grid-pane'); const areaUnitSpan = $('#area-unit'); const cellAreaUnitSpan = $('#cell-area-unit'); const elevUnit1 = $('#elev-unit-1'); const elevUnit2 = $('#elev-unit-2'); const topsoilUnit = $('#topsoil-unit'); const inputs = { topsoil: $('#topsoil'), swell: $('#swell'), compaction: $('#compaction'), simpleArea: $('#simple-area'), simpleExisting: $('#simple-existing'), simpleDesign: $('#simple-design'), cellArea: $('#cell-area'), gridBody: $('#grid-body'), gridError: $('#grid-error'), }; const results = { cutInPlace: $('#cut-inplace'), fillInPlace: $('#fill-inplace'), netInPlace: $('#net-inplace'), looseCut: $('#loose-cut'), borrowLoose: $('#borrow-loose'), balance: $('#balance-text'), altUnits: $('#alt-units'), }; const errors = { topsoil: $('#topsoil-error'), swell: $('#swell-error'), compaction: $('#compaction-error'), simpleArea: $('#simple-area-error'), simpleExisting: $('#simple-existing-error'), simpleDesign: $('#simple-design-error'), cellArea: $('#cell-area-error'), }; const buttons = { addRow: $('#add-row'), add5Rows: $('#add-5-rows'), clearRows: $('#clear-rows'), }; // State let mode = 'simple'; // simple | grid let units = 'metric'; // metric | imperial // Utils const clamp = (val, min, max) => Math.min(Math.max(val, min), max); const toNumber = v => { const n = typeof v === 'number' ? v : parseFloat(String(v).replace(/,/g,'')); return Number.isFinite(n) ? n : NaN; }; // Unit helpers const m3_to_yd3 = v => v * 1.30795062; const yd3_to_m3 = v => v * 0.764554858; const ft3_to_yd3 = v => v / 27; function formatNumber(n){ if (!Number.isFinite(n)) return '—'; return n.toLocaleString(undefined, {maximumFractionDigits: 2}); } function updateUnitLabels(){ if (units === 'metric'){ areaUnitSpan.textContent = 'm²'; cellAreaUnitSpan.textContent = 'm²'; elevUnit1.textContent = 'm'; elevUnit2.textContent = 'm'; topsoilUnit.textContent = 'm'; } else { areaUnitSpan.textContent = 'ft²'; cellAreaUnitSpan.textContent = 'ft²'; elevUnit1.textContent = 'ft'; elevUnit2.textContent = 'ft'; topsoilUnit.textContent = 'ft'; } } function toggleModeUI(){ if (mode === 'simple'){ simplePane.hidden = false; gridPane.hidden = true; } else { simplePane.hidden = true; gridPane.hidden = false; if (inputs.gridBody.children.length === 0){ addRows(5); } } } // Accessible tooltip toggles
('#grid-body tr').forEach((tr, i) => { const idx = tr.querySelector('.row-index'); if (idx) idx.textContent = i + 1; const [existing, design, actionTd] = tr.querySelectorAll('td'); const inpExisting = tr.querySelector('td:nth-child(2) input'); const inpDesign = tr.querySelector('td:nth-child(3) input'); if (inpExisting) inpExisting.setAttribute('aria-label', `Row ${i+1} existing elevation`); if (inpDesign) inpDesign.setAttribute('aria-label', `Row ${i+1} design elevation`); const btn = tr.querySelector('button.btn.secondary'); if (btn) btn.setAttribute('aria-label', `Remove row ${i+1}`); }); } function addRows(n=1){ for (let i=0; i<n; i++){ const row = makeRow(inputs.gridBody.children.length); inputs.gridBody.appendChild(row); } } buttons.addRow.addEventListener('click', () => addRows(1)); buttons.add5Rows.addEventListener('click', () => addRows(5)); buttons.clearRows.addEventListener('click', () => { inputs.gridBody.innerHTML = ''; inputs.gridError.textContent = ''; calculate(); }); // Validation function setError(inputEl, errorEl, message){ if (!errorEl) return; if (message){ inputEl?.setAttribute('aria-invalid', 'true'); errorEl.textContent = message; } else { inputEl?.setAttribute('aria-invalid', 'false'); errorEl.textContent = ''; } } function validateRequired(inputEl, errorEl, opts={}){ const v = toNumber(inputEl.value); if (!Number.isFinite(v) || (opts.min != null && v < opts.min)){ setError(inputEl, errorEl, opts.message || 'Please enter a valid number.'); return false; } setError(inputEl, errorEl, ''); return true; } function validateFactors(){ const ok1 = validateRequired(inputs.swell, errors.swell, {min:0, message:'Enter swell as a percentage ≥ 0.'}); const c = toNumber(inputs.compaction.value); let ok2 = true; if (!Number.isFinite(c) || c < 0 || c >= 100){ setError(inputs.compaction, errors.compaction, 'Compaction must be between 0 and 99.9%.'); ok2 = false; } else { setError(inputs.compaction, errors.compaction, ''); } const ok3 = validateRequired(inputs.topsoil, errors.topsoil, {min:0, message:'Topsoil strip must be ≥ 0.'}); return ok1 && ok2 && ok3; } // Event wiring modeRadios.forEach(r => r.addEventListener('change', e => { mode = e.target.value; toggleModeUI(); calculate(); })); unitRadios.forEach(r => r.addEventListener('change', e => { units = e.target.value; updateUnitLabels(); calculate(); })); [inputs.topsoil, inputs.swell, inputs.compaction, inputs.simpleArea, inputs.simpleExisting, inputs.simpleDesign, inputs.cellArea ].forEach(el => { if (!el) return; el.addEventListener('input', debounce(calculate, 100)); }); inputs.simpleArea.addEventListener('blur', () => validateRequired(inputs.simpleArea, errors.simpleArea, {min:0, message:'Area is required and must be ≥ 0.'})); inputs.simpleExisting.addEventListener('blur', () => validateRequired(inputs.simpleExisting, errors.simpleExisting, {message:'Enter a valid elevation.'})); inputs.simpleDesign.addEventListener('blur', () => validateRequired(inputs.simpleDesign, errors.simpleDesign, {message:'Enter a valid elevation.'})); inputs.cellArea.addEventListener('blur', () => validateRequired(inputs.cellArea, errors.cellArea, {min:0, message:'Cell area is required and must be ≥ 0.'})); // Debounce utility function debounce(fn, wait=120){ let t; return function(...args){ clearTimeout(t); t = setTimeout(() => fn.apply(this, args), wait); } } // Core calculation function calculate(){ if (!validateFactors()){ writeResults(0,0,0,0,0,'—',0,true); return; } const topsoil = Math.max(0, toNumber(inputs.topsoil.value) || 0); const swellPct = Math.max(0, toNumber(inputs.swell.value) || 0); const compPct = clamp(toNumber(inputs.compaction.value) || 0, 0, 99.9); let cut_inplace_m3 = 0; let fill_inplace_m3 = 0; if (mode === 'simple'){ const okA = validateRequired(inputs.simpleArea, errors.simpleArea, {min:0, message:'Area is required and must be ≥ 0.'}); const okE = validateRequired(inputs.simpleExisting, errors.simpleExisting, {message:'Enter a valid elevation.'}); const okD = validateRequired(inputs.simpleDesign, errors.simpleDesign, {message:'Enter a valid elevation.'}); if (!okA || !okE || !okD){ writeResults(0,0,0,0,0,'—',0,true); return; } const area = Math.max(0, toNumber(inputs.simpleArea.value) || 0); const E = toNumber(inputs.simpleExisting.value) || 0; const D = toNumber(inputs.simpleDesign.value) || 0; const delta = (E - topsoil) - D; // positive -> cut, negative -> fill if (units === 'metric'){ if (delta > 0) cut_inplace_m3 = delta * area; else fill_inplace_m3 = -delta * area; } else { // Imperial inputs: ft, ft². Convert ft³ -> m³. const vol_ft3 = (delta > 0 ? delta * area : -delta * area); const vol_m3 = vol_ft3 * 0.028316846592; // 1 ft³ = 0.028316846592 m³ if (delta > 0) cut_inplace_m3 = vol_m3; else fill_inplace_m3 = vol_m3; } } else { // Grid const okCA = validateRequired(inputs.cellArea, errors.cellArea, {min:0, message:'Cell area is required and must be ≥ 0.'}); if (!okCA){ writeResults(0,0,0,0,0,'—',0,true); return; } const cellArea = Math.max(0, toNumber(inputs.cellArea.value) || 0); let anyRow = false; inputs.gridError.textContent = '';
For each cell i with area A_i and adjusted height difference Δh_i between existing and design grades: \( \Delta h_i = (E^{\text{existing}}_i - t) - E^{\text{design}}_i \) \( V_{\text{cut}} = \sum_{i} \max(\Delta h_i, 0)\cdot A_i \quad,\quad V_{\text{fill}} = \sum_{i} \max(-\Delta h_i, 0)\cdot A_i \) Adjustments for bulking (swell) and compaction (shrinkage): \( V_{\text{cut, loose}} = V_{\text{cut}} \cdot (1 + s) \quad,\quad V_{\text{borrow, loose}} = \dfrac{V_{\text{fill}}}{1 - c} \) Where t is topsoil strip thickness, s and c are decimals (e.g., 20% → 0.20).
\( \Delta h_i = (E^{\text{existing}}_i - t) - E^{\text{design}}_i \)
\( V_{\text{cut}} = \sum_{i} \max(\Delta h_i, 0)\cdot A_i \quad,\quad V_{\text{fill}} = \sum_{i} \max(-\Delta h_i, 0)\cdot A_i \)
\( V_{\text{cut, loose}} = V_{\text{cut}} \cdot (1 + s) \quad,\quad V_{\text{borrow, loose}} = \dfrac{V_{\text{fill}}}{1 - c} \)
- No variables provided in audit spec.
- Direct PDF — publications.usace.army.mil · Accessed 2026-01-19
https://www.publications.usace.army.mil/Portals/76/Publications/EngineerManuals/EM_1110-2-2300.pdf - Esri’s Cut/Fill Volume Calculation — enterprise.arcgis.com · Accessed 2026-01-19
https://enterprise.arcgis.com/en/portal/11.5/use/cut-and-fill-volume-calculation.htm
Last code update: 2026-01-19
- Initial audit spec draft generated from HTML extraction (review required).
- Verify formulas match the calculator engine and convert any text-only formulas to LaTeX.
- Confirm sources are authoritative and relevant to the calculator methodology.