Resources Numerical accuracy Calculation policy

Layout point files and as-built comparison

Writes points for a total station as a text file, reads such a file back, and compares as-built points with the design.

How the precision is kept

Points are 64-bit inside the app. The file holds them as text with 3 decimals by default, so a point written and read back agrees with the original to half of the last decimal. The comparison works on the 64-bit values and rounds only the table it prints.

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.

layout/layout-points.js CadCraft 0.8.2 64-bit 6 calculations Reviewed

Tests bin/test-layout.mjs

Points written to a file

64-bit

Each point's northing, easting and elevation as text.

text = v.toFixed(3); 0 when |v| < 0.0005

The coordinate is rounded once, by toFixed, to the number of decimals asked for (3 by default). A value below half of the last decimal is written as 0 so the file never holds −0.000.

  • layout/layout-points.js
  • lines 35 to 41
  • CadCraft 0.8.2
export function formatPoints(points, { format = 'PNEZD', delimiter = ',', dec = 3 } = {}) {
  const cols = POINT_FORMATS[format] || POINT_FORMATS.PNEZD;
  const q = (s) => (s.includes(delimiter) || /["\r\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s);
  const num = (v) => (Number.isFinite(v) ? (Math.abs(v) < 0.5 * 10 ** -dec ? 0 : v).toFixed(dec) : '');
  const cell = (p, c) => ({ P: q(p.n), N: num(p.y), E: num(p.x), Z: num(p.z), D: q(p.d || '') })[c];
  return points.map((p) => cols.map((c) => cell(p, c)).join(delimiter)).join('\r\n') + (points.length ? '\r\n' : '');
}

Points read from a file

64-bit

Turns the northing, easting and elevation text of a row into numbers.

x = Number(E); y = Number(N); z = Number(Z) or none

Each number is converted once from the file's text to the nearest 64-bit value, with every digit the file holds. An empty elevation stays empty, it does not become 0.

  • layout/layout-points.js
  • lines 81 to 89
  • CadCraft 0.8.2
const N = c[iN], E = c[iE], Z = c[iZ];
if (!isNum(N) || !isNum(E)) { bad(r, 'ne'); continue; }
if (Z !== undefined && Z !== '' && !isNum(Z)) { bad(r, 'z'); continue; }
let n = iP >= 0 ? String(c[iP] ?? '').trim() : '';
if (iP >= 0 && !n) { bad(r, 'nonum'); continue; }
if (iP < 0) { do n = String(++auto); while (seen.has(n)); }
if (seen.has(n)) { out.duplicates++; bad(r, 'twice', { num: n }); continue; }
seen.add(n);
out.points.push({ n: cleanText(n), x: Number(E), y: Number(N), z: Z === undefined || Z === '' ? null : Number(Z), d: iD >= 0 ? cleanText(c[iD] ?? '') : '' });

Difference between a design and an as-built point

64-bit

The offsets in x, y and height, the horizontal and 3D distance, and whether the point is within tolerance.

dh = √(dx² + dy²); d3 = √(dh² + dz²); out when dh > tol or |dz| > tolZ

The offsets are differences of coordinates that are usually large and close together, so their absolute error is that of the coordinates. The tolerance test adds 1e-9, an absolute allowance in the file's units.

  • layout/layout-points.js
  • lines 146 to 152
  • CadCraft 0.8.2
const pair = (d, b) => {
  const dx = b.x - d.x, dy = b.y - d.y;
  const dz = Number.isFinite(d.z) && Number.isFinite(b.z) && d.z !== null && b.z !== null ? b.z - d.z : null;
  const dh = Math.hypot(dx, dy), d3 = dz === null ? dh : Math.hypot(dh, dz);
  const status = dh > tol + 1e-9 || (dz !== null && Math.abs(dz) > tz + 1e-9) ? 'out' : 'ok';
  return { n: d.n, built: b.n, status, dx, dy, dz, dh, d3, design: { x: d.x, y: d.y, z: d.z ?? null }, asBuilt: { x: b.x, y: b.y, z: b.z ?? null }, d: d.d || b.d || '' };
};

Matching points by nearness

64-bit

Pairs each design point with the closest unused as-built point within a radius.

pairs sorted by horizontal distance, taken closest first; radius = 10 × tol by default

Distances are Math.hypot values compared directly. Two pairs at exactly the same distance are taken in the order the sort leaves them.

  • layout/layout-points.js
  • lines 153 to 160
  • CadCraft 0.8.2
if (match === 'nearest') {
  const R = radius ?? 10 * tol;
  const cand = [];
  design.forEach((d, i) => built.forEach((b, j) => { const h = Math.hypot(b.x - d.x, b.y - d.y); if (h <= R) cand.push([h, i, j]); }));
  cand.sort((a, b) => a[0] - b[0]);
  const got = new Map();
  for (const [, i, j] of cand) { if (got.has(i) || used.has(j)) continue; got.set(i, j); used.add(j); }
  design.forEach((d, i) => rows.push(got.has(i) ? pair(d, built[got.get(i)]) : missing(d)));

Summary of the comparison

64-bit

The counts, the largest horizontal and height differences and the root mean square of the horizontal differences.

rms = √(Σ dh² ÷ n)

A sum of squares added in row order, so nothing cancels.

  • layout/layout-points.js
  • lines 172 to 178
  • CadCraft 0.8.2
const summary = {
  design: design.length, built: built.length, matched: m.length,
  ok: rows.filter((r) => r.status === 'ok').length, out: rows.filter((r) => r.status === 'out').length,
  missing: rows.filter((r) => r.status === 'missing').length, extra: rows.filter((r) => r.status === 'extra').length,
  maxH: m.reduce((a, r) => Math.max(a, r.dh), 0), maxZ: m.reduce((a, r) => Math.max(a, Math.abs(r.dz ?? 0)), 0),
  rmsH: m.length ? Math.sqrt(m.reduce((a, r) => a + r.dh * r.dh, 0) / m.length) : 0,
};

Comparison table

64-bit

The rows of the comparison as text for the page and the CSV.

text = v.toFixed(3)

Coordinates and differences are written with toFixed, 3 decimals by default. The CSV therefore holds the rounded text, not the full differences.

  • layout/layout-points.js
  • lines 183 to 187
  • CadCraft 0.8.2
export function compareTable(cmp, { head, status, dec = 3 }) {
  const f = (v) => (v === null || v === undefined || !Number.isFinite(v) ? '' : v.toFixed(dec));
  return [head, ...cmp.rows.map((r) => [r.n, r.built, status[r.status] || r.status, r.d,
    f(r.design?.y), f(r.design?.x), f(r.design?.z), f(r.asBuilt?.y), f(r.asBuilt?.x), f(r.asBuilt?.z), f(r.dx), f(r.dy), f(r.dz), f(r.dh)])];
}

In detail

Method
Fixed decimals on writing; JavaScript's Number() on reading; horizontal and 3D distance by Math.hypot; root mean square of the horizontal distances.
Number types
64-bit numbers; text with a fixed number of decimals in the file.
Tolerances
A point is out when its horizontal distance exceeds tol + 1e-9 or its height difference exceeds the height tolerance + 1e-9; the 1e-9 is absolute, in the file's units.
Limits
The exported file carries 3 decimals unless another number is asked for.

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