Resources Numerical accuracy Calculation policy

Traverse closure and compass adjustment

Runs a survey traverse from field angles or bearings and distances, reports its angular and linear misclosure and precision, and adjusts the stations by the compass rule.

How the precision is kept

Azimuths are carried in degrees and turned to radians once per leg; coordinates are running 64-bit sums of distance × cosine and sine. The angular misclosure is spread equally over the angles and the linear misclosure in proportion to the distance run. Results keep full precision; the report writes coordinates to 4 decimals and angles to whole seconds.

Calculations under this policy

4 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.

traverse.js CivilCraft 0.8.1 64-bit 4 calculations Reviewed

Tests bin/test-civilcraft-traverse.mjs

Angular misclosure and leg directions

64-bit

The angular misclosure of a loop and the direction of each leg after the angles are corrected.

misclosure = ((again − first) mod 360 + 540) mod 360 − 180; per angle = −misclosure ÷ n; az(i) = az(i − 1) + 180 + angle(i) + per angle

Sums of degrees in 64-bit. Whole-degree parts add exactly; the misclosure is a difference of two accumulated azimuths, so it carries the rounding of the fractional parts of the angles. The azimuth grows by 180 degrees and more per leg and is not reduced before it is converted to radians.

  • traverse.js
  • lines 74 to 88
  • CivilCraft 0.8.1
let dirs, angular = { misclosure: null, perAngle: 0 };
if (mode === 'angles') {
  const a = legs.map((l) => l.angle);
  if (close === 'loop' && closingAngle !== null && n >= 2) {
    let az = backsight + a[0];
    for (let i = 1; i < n; i++) az += 180 + a[i];
    const again = az + 180 + closingAngle, first = backsight + a[0];
    const mis = ((((again - first) % 360) + 540) % 360) - 180;
    angular = { misclosure: mis, perAngle: -mis / n };
  }
  dirs = [];
  let az = backsight + a[0];
  dirs.push(az2dir(az));
  for (let i = 1; i < n; i++) { az += 180 + a[i] + angular.perAngle; dirs.push(az2dir(az)); }
} else dirs = legs.map((l) => l.dir);

Station coordinates and linear misclosure

64-bit

The unadjusted coordinates of each station, the misclosure at the end and the traverse's precision.

x += d·cos(dir); y += d·sin(dir); misclosure = √(Δx² + Δy²); precision = total length ÷ misclosure

Coordinates are running sums from the start point, one term per leg, so rounding accumulates with the number of legs. The misclosure subtracts the end point from a coordinate of the same size, which is where a traverse with large coordinates loses digits; the code does not shift to a local origin.

  • traverse.js
  • lines 90 to 97
  • CivilCraft 0.8.1
const st = [];
let x = start.x, y = start.y, length = 0;
for (let i = 0; i < n; i++) {
  x += legs[i].dist * Math.cos(dirs[i]); y += legs[i].dist * Math.sin(dirs[i]); length += legs[i].dist;
  st.push({ name: legs[i].name || '', x, y, dir: dirs[i], dist: legs[i].dist, run: length });
}
const end = close === 'loop' ? start : close;
const dx = x - end.x, dy = y - end.y, dist = Math.hypot(dx, dy);

Compass rule adjustment

64-bit

Moves each station against the misclosure in proportion to the distance run to reach it.

cx = −Δx × run ÷ length; cy = −Δy × run ÷ length; adjusted = unadjusted + correction

One 64-bit quotient and product per coordinate. The last station's run equals the total length, so its correction is exactly minus the misclosure.

  • traverse.js
  • lines 99 to 101
  • CivilCraft 0.8.1
for (const s of st) { s.cx = -dx * (s.run / length); s.cy = -dy * (s.run / length); s.ax = s.x + s.cx; s.ay = s.y + s.cy; }
if (n < 2) problems.push(t('tv.few'));
return { stations: st, angular, linear: { dx, dy, dist, dir: Math.atan2(dy, dx) }, length, precision: dist > 1e-12 ? length / dist : Infinity, problems, start, close };

Traverse report as CSV

64-bit

Writes the closure and every station's unadjusted and adjusted coordinates and corrections.

coordinates and corrections to 4 decimals; distances and length to 3; precision as 1 : whole number; angles to whole seconds

The exported text is rounded with toFixed; the angular misclosure and bearings are rounded to whole seconds. The values in the result object are not rounded.

  • traverse.js
  • lines 104 to 119
  • CivilCraft 0.8.1
export function traverseReport(r, { units = 'ft' } = {}) {
  const u = units === 'm' ? 'm' : 'ft';
  const q = (v) => (/^-?\d/.test(v) ? v : /^[=+\-@\t\r]/.test(v) ? "'" + v : v);   // text that a spreadsheet would run as a formula gets a leading '
  const c = (v) => (/[",\r\n]/.test(v) ? '"' + v.replace(/"/g, '""') + '"' : v);
  const rows = [
    [t('tv.r.angular'), r.angular.misclosure === null ? '' : (r.angular.misclosure < 0 ? '-' : '') + dms(Math.abs(r.angular.misclosure))],
    [t('tv.r.linear', { u }), fixed(r.linear.dist, 4), bearing(r.linear.dir)],
    [t('tv.r.length', { u }), fixed(r.length, 3)],
    [t('tv.r.precision'), Number.isFinite(r.precision) ? '1 : ' + Math.round(r.precision) : t('tv.r.exact')],
    [],
    [t('tv.r.station'), t('tv.r.dir'), t('tv.r.dist'), t('tv.r.n'), t('tv.r.e'), t('tv.r.cn'), t('tv.r.ce'), t('tv.r.an'), t('tv.r.ae')],
    [t('tv.r.start'), '', '', fixed(r.start.y, 4), fixed(r.start.x, 4), '0.0000', '0.0000', fixed(r.start.y, 4), fixed(r.start.x, 4)],
    ...r.stations.map((s, i) => [s.name || String(i + 1), bearing(s.dir), fixed(s.dist, 3), fixed(s.y, 4), fixed(s.x, 4), fixed(s.cy, 4), fixed(s.cx, 4), fixed(s.ay, 4), fixed(s.ax, 4)]),
  ];
  return rows.map((r2) => r2.map((v) => c(q(String(v)))).join(',')).join('\r\n') + '\r\n';
}

In detail

Method
az(i) = az(i − 1) + 180° + angle(i); angular misclosure spread equally; compass (Bowditch) rule: correction = −misclosure × distance run ÷ total length. Stated in the file header.
Number types
64-bit numbers.
Tolerances
Precision is reported as infinite when the linear misclosure is 1e-12 or less.
Limits
The azimuth is not reduced to 0 to 360 degrees as it accumulates, so it grows by more than 180 degrees per leg before the sine and cosine are taken. The report rounds to 4 decimals, the precision ratio to a whole number and angles to whole seconds.

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