Resources Numerical accuracy Calculation policy

Data collector files and polar reduction

Reads total-station field books (RW5, GSI, JobXML, CSV), reduces each shot to coordinates, and prepares directions and distances for the network adjustment.

How the precision is kept

Numbers are parsed to 64-bit and reduced with one expression per coordinate. What the files hold sets the accuracy: angles written as DDD.MMSSss are read to hundredths of a second, and GSI words are whole counts of a fixed unit. No earth-curvature or refraction term is applied to height differences.

Open item. Read a decimal comma in JobXML text as a decimal point instead of removing it.

Calculations under this policy

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

collector.js CivilCraft 0.8.1 64-bit 6 calculations Reviewed

Tests bin/test-civilcraft-collector.mjs, bin/test-civilcraft-geo3.mjs

Number from file text

64-bit

A coordinate, angle, distance or height read from a field of the file.

Every character that is not a digit, sign, point or e is removed before the text is converted, so a unit suffix is ignored. A comma is removed too: 105,32 would read as 10532. RW5 and CSV lines are split on commas first, so this can only occur in JobXML text.

  • collector.js
  • line 19
  • CivilCraft 0.8.1
const num = (v) => { const x = Number(String(v).replace(/[^\d.+\-eE]/g, '')); return Number.isFinite(x) && String(v).trim() !== '' ? x : NaN; };

Dotted degrees, minutes, seconds to radians

64-bit

An angle written as DDD.MMSSss, as many data collectors write it, as radians.

angle = (D + M ÷ 60 + S ÷ 3600) × π ÷ 180

The fractional part is rounded to 6 decimals before it is split, which removes the binary rounding of the typed value and also drops anything finer than a hundredth of a second. 1e-7 is added before the minutes are taken, so that a product such as 45.99999999999999, which stands for 46 minutes, is not cut to 45.

  • collector.js
  • lines 20 to 21
  • CivilCraft 0.8.1
/** DDD.MMSSss → radians (the dotted degrees-minutes-seconds many data collectors write). */
export function dmsDotted(v) { const s = Math.abs(v), d = Math.floor(s), f = Math.round((s - d) * 1e6) / 1e6, m = Math.floor(f * 100 + 1e-7), sec = (f * 100 - m) * 100; return Math.sign(v || 1) * (d + m / 60 + sec / 3600) * D2R; }

Degrees or gons to radians

64-bit

A file angle in decimal degrees or gons as radians.

degrees × π ÷ 180; gons × π ÷ 200

One multiplication by the 64-bit value of π and one division. π is the nearest 64-bit value.

  • collector.js
  • line 22
  • CivilCraft 0.8.1
const toRad = (v, kind) => (kind === 'dms' ? dmsDotted(v) : kind === 'gon' ? v * Math.PI / 200 : v * D2R);

GSI word to a length or angle

64-bit

The value of a GSI data word from its digits and its unit code.

millimeters ÷ 1000; 1/10 mm ÷ 10⁴; 1/100 mm ÷ 10⁵; gon or degree counts ÷ 10⁵; sexagesimal digits split into D, M, S

The digits are a whole number, so the only rounding is the single division by a power of ten and, for angles, the multiplication by π. The resolution is that of the word: 1 mm down to 0.01 mm for lengths; 1e-4 or 1e-5 gon, 1e-5 degree or a tenth of a second for angles.

  • collector.js
  • lines 50 to 55
  • CivilCraft 0.8.1
const unit = (info, raw) => {
  const sign = raw[0] === '-' ? -1 : 1, digits = raw.replace(/^[+-]/, ''), u = info.slice(-1), n = Number(digits);
  switch (u) { case '0': return { v: sign * n / 1000, k: 'len' }; case '1': return { v: sign * n / 1000, k: 'len' }; case '2': return { v: sign * n / 1e5 * Math.PI / 200, k: 'ang' };
    case '3': return { v: sign * n / 1e5 * D2R, k: 'ang' }; case '4': { const s = String(Math.abs(n)).padStart(9, '0'); const d = Number(s.slice(0, -5)), m = Number(s.slice(-5, -3)), sc = Number(s.slice(-3, -1)) + Number(s.slice(-1)) / 10; return { v: sign * (d + m / 60 + sc / 3600) * D2R, k: 'ang' }; }
    case '6': return { v: sign * n / 1e4 * Math.PI / 200, k: 'ang' }; case '7': return { v: sign * n / 1e4, k: 'len' }; case '8': return { v: sign * n / 1e5, k: 'len' }; default: return { v: NaN, k: '?' }; }
};

Polar reduction

64-bit

Northing, easting and elevation of every shot from its setup.

orientation = atan2(ΔE, ΔN) to the backsight − circle reading; hd = sd·sin(vz)·scale; N = N₀ + hd·cos(az), E = E₀ + hd·sin(az); Z = Z₀ + sd·cos(vz) + hi − hr

Each coordinate is one expression from the station; nothing accumulates from shot to shot. The scale factor multiplies the horizontal distance only. No earth-curvature or refraction term is added to the height difference. A setup without a usable backsight is reduced with orientation 0 and reported.

  • collector.js
  • lines 134 to 150
  • CivilCraft 0.8.1
/** Polar reduction. Station coordinates are northing/easting (n, e); azimuth clockwise from north; the backsight fixes the orientation. */
export function reduce(job, { scale = 1 } = {}) {
  const points = {}, shots = [], problems = [];
  for (const s of job.setups) {
    const st = s.coords || job.points[s.station];
    if (!st) { problems.push({ code: 'col.nostation', station: s.station }); continue; }
    const bs = s.backsight && job.points[s.backsight.id];
    let orient = 0;
    if (bs && s.backsight.hz !== null) orient = Math.atan2(bs.e - st.e, bs.n - st.n) - s.backsight.hz; else problems.push({ code: 'col.nobacksight', station: s.station });
    for (const k of s.shots) {
      const hd = (k.horizontal ? k.sd : k.sd * Math.sin(k.vz)) * scale, az = orient + k.hz, dz = k.horizontal ? 0 : k.sd * Math.cos(k.vz) + s.hi - k.hr;
      const p = { n: st.n + hd * Math.cos(az), e: st.e + hd * Math.sin(az), z: Number.isFinite(st.z) ? st.z + dz : NaN, code: k.code };
      shots.push({ station: s.station, id: k.id, az, hd, ...p }); if (k.id && !points[k.id]) points[k.id] = p;
    }
  }
  return { points, shots, problems };
}

Shots as adjustment observations

64-bit

Directions and horizontal distances with their standard deviations, and starting coordinates for the free points.

σ(direction) = 5″ = (5 ÷ 3600) × π ÷ 180; σ(distance) = 0.005

The distance observation is the reduced horizontal distance at scale 1. Starting coordinates for free points are the polar reduction's. The default standard deviations are fixed numbers, not read from the file.

  • collector.js
  • lines 152 to 170
  • CivilCraft 0.8.1
/** Turn a job into an adjustment: stations with coordinates + known backsights are control; every other shot target is free. x = easting, y = northing. */
export function toObservations(job, { sdDir = (5 / 3600) * D2R, sdDist = 0.005, sdCtrl = 0 } = {}) {
  const pts = {}, obs = []; const red = reduce(job);
  const known = (id) => job.points[id];
  for (const [id, p] of Object.entries(job.points)) pts[id] = { x: p.e, y: p.n, fixed: true, ...(sdCtrl ? { sx: sdCtrl, sy: sdCtrl } : {}) };
  const free = new Set();
  job.setups.forEach((s, k) => {
    if (!pts[s.station]) return;
    if (s.backsight?.id && pts[s.backsight.id] && s.backsight.hz !== null) obs.push({ type: 'dir', at: s.station, to: s.backsight.id, value: s.backsight.hz, sd: sdDir, set: k });
    for (const sh of s.shots) {
      const r = red.shots.find((q) => q.station === s.station && q.id === sh.id); if (!sh.id || !r) continue;
      if (!pts[sh.id]) { pts[sh.id] = { x: r.e, y: r.n }; free.add(sh.id); }
      obs.push({ type: 'dir', at: s.station, to: sh.id, value: sh.hz, sd: sdDir, set: k });
      obs.push({ type: 'dist', from: s.station, to: sh.id, value: r.hd, sd: sdDist });
    }
  });
  void known;
  return { points: pts, obs, free: [...free] };
}

In detail

Method
Polar reduction from each setup: orientation from the backsight, horizontal distance = slope distance × sin(zenith angle), height difference = slope distance × cos(zenith angle) + instrument height − target height.
Number types
64-bit numbers. Angles are held in radians.
Tolerances
Dotted degrees-minutes-seconds: the fractional part is rounded to 6 decimals, with 1e-7 added before the minutes are split off. Default standard deviations handed to the adjustment: 5 seconds of arc for directions and 0.005 plan units for distances.
Limits
The file header says the readers were tested on files written from the formats' public descriptions and that no real instrument file has been through them yet. The reduce function takes a scale factor only; curvature and refraction are not modeled.

Approach: 64-bit, stated limit

The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.

Every policy that takes this approach