Resources Numerical accuracy Calculation policy

Staking reports

Lists stakes along an alignment at stations and offsets, or survey points measured against it, each with its coordinates, design and ground elevations and cut or fill.

How the precision is kept

Each stake is placed in 64-bit from the alignment point at its station and one sine and cosine of the direction. Cut or fill is one subtraction of the design from the ground. The rows keep full precision; the CSV writes coordinates, elevations and cut or fill to 3 decimals, and the PDF table writes elevations to 2.

Calculations under this policy

5 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

staking.js CivilCraft 0.8.1 64-bit 5 calculations Reviewed

Tests bin/test-civilcraft-staking.mjs

Stations to stake

64-bit

Every multiple of the step between two stations, both ends, and the alignment's own curve points in between.

sta = k × step for whole k; neighbors closer than 1e-6 merged

Each station is k × step, one multiplication, clamped to the range, and is not rounded. The first k uses ceil((a − 1e-9) ÷ step) so a start on a multiple of the step is kept.

  • staking.js
  • lines 28 to 34
  • CivilCraft 0.8.1
export function stakeStations(al, { from = al.start, to = al.end, step = 50, keys = true } = {}) {
  const a = Math.max(al.start, Math.min(from, to)), b = Math.min(al.end, Math.max(from, to));
  const set = new Set([a, b]);
  if (step > 0) for (let k = Math.ceil((a - EPS) / step); k * step <= b + EPS; k++) set.add(Math.min(b, Math.max(a, k * step)));
  if (keys) for (const e of al.elements || []) for (const s of [e.sta, e.sta + e.len]) if (s > a + EPS && s < b - EPS) set.add(s);
  return [...set].sort((x, y) => x - y).filter((s, i, arr) => i === 0 || s - arr[i - 1] > 1e-6);
}

Stake position

64-bit

The coordinates of a stake at a station and an offset to the right or left.

x = xₚ + off × sin(dir); y = yₚ − off × cos(dir)

One 64-bit sine and cosine of the alignment's direction at the station and one multiply and add per coordinate.

  • staking.js
  • lines 36 to 40
  • CivilCraft 0.8.1
function place(al, sta, off) {
  const p = al.pointAt(sta);
  // right of the alignment = + : the right-hand normal of the forward direction
  return { x: p.x + off * Math.sin(p.dir), y: p.y - off * Math.cos(p.dir) };
}

Stake rows with cut or fill

64-bit

One row per station and offset with design elevation, ground elevation and cut or fill.

cf = ground − design (positive = cut)

A single 64-bit subtraction of two elevations supplied by the caller; the row keeps the unrounded value.

  • staking.js
  • lines 41 to 50
  • CivilCraft 0.8.1
export function stakeRows(al, { stations, offsets = [0], design = () => null, ground = () => null, first = 1, prefix = '' }) {
  const rows = [];
  let n = first;
  for (const sta of stations) for (const off of offsets) {
    const { x, y } = place(al, sta, off);
    const d = design(sta, off, x, y), g = ground(x, y);
    rows.push({ id: prefix + n++, sta, off, x, y, design: fin(d) ? d : null, ground: fin(g) ? g : null, cf: fin(d) && fin(g) ? g - d : null });
  }
  return rows;
}

Survey points against the alignment

64-bit

Each survey point's station and offset on the alignment, the design there and the cut or fill to the point's own elevation.

cf = point elevation − design

Station and offset come from the alignment's own stationOffset; the cut or fill is one 64-bit subtraction. Points beyond the alignment's ends are counted and left out.

  • staking.js
  • lines 51 to 62
  • CivilCraft 0.8.1
export function pointRows(al, points, { design = () => null, ground = null } = {}) {
  const rows = [];
  let outside = 0;
  for (const p of points) {
    const so = al.stationOffset(p.x, p.y);
    if (!so || so.outside) { outside++; continue; }
    const d = design(so.sta, so.off, p.x, p.y), g = fin(p.z) ? p.z : ground ? ground(p.x, p.y) : null;
    rows.push({ id: p.n, sta: so.sta, off: so.off, x: p.x, y: p.y, design: fin(d) ? d : null, ground: fin(g) ? g : null, cf: fin(d) && fin(g) ? g - d : null });
  }
  rows.sort((a, b) => a.sta - b.sta || a.off - b.off);
  return { rows, outside };
}

Staking table as CSV

64-bit

Writes the stake rows to a CSV file.

northing, easting, design, ground and cut or fill to 3 decimals; offset and the C/F column to 2 decimals

The exported numbers are rounded with toFixed: 3 decimals for coordinates, elevations and the signed cut or fill, 2 for the offset and the C or F text. A cut or fill smaller than 0.005 is written as 0.00 in the text column.

  • staking.js
  • lines 90 to 99
  • CivilCraft 0.8.1
export function stakingCsv(rows, { units = 'ft', title = '' } = {}) {
  const u = units === 'm' ? 'm' : 'ft';
  const q = (v) => { const s = String(v); return /^-?\d/.test(s) || s === '' ? s : /^[=+\-@\t\r]/.test(s) ? "'" + s : s; };
  const c = (v) => (/[",\r\n]/.test(v) ? '"' + v.replace(/"/g, '""') + '"' : v);
  const out = [];
  if (title) out.push([title]);
  out.push([t('sk.c.point'), t('sk.c.sta'), t('sk.c.off', { u }), t('sk.c.n'), t('sk.c.e'), t('sk.c.design'), t('sk.c.ground'), t('sk.c.cf', { u }), t('sk.c.cfn')]);
  for (const r of rows) out.push([r.id, formatStation(r.sta, units), offsetText(r.off), num(r.y, 3), num(r.x, 3), num(r.design, 3), num(r.ground, 3), cutFillText(r.cf), num(r.cf, 3)]);
  return out.map((r) => r.map((v) => c(q(v))).join(',')).join('\r\n') + '\r\n';
}

In detail

Method
Stake = alignment point + offset × right-hand normal; cut or fill = ground − design.
Number types
64-bit numbers.
Tolerances
Station steps and key stations compared with 1e-9; stations closer than 1e-6 merged; offsets closer than 1e-9 treated as repeats; a cut, fill or offset below 0.005 prints as 0.00.
Limits
The exported text is rounded: 3 decimals in the CSV, 2 or 3 in the PDF table.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach