Resources Numerical accuracy Calculation policy

IFC 4.3 infrastructure quantities and files

Reads the quantities, stations and alignment lengths of roads, railways, bridges and other infrastructure from an IFC file, totals them per class, and writes a new IFC 4.3 file.

How the precision is kept

Quantities are the file's own stated values, converted to SI by one multiplication in 64-bit and summed per class. Two rounding steps then set what is kept: totals are rounded to 12 significant digits, and every number written to a new file has 9 decimals (12 for direction components).

Open item. Scale prefixed SI area and volume units by the square and cube of the prefix, and keep the totals unrounded (or round to 15 significant digits).

Calculations under this policy

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

ifc/ifc43.js BimCraft 0.8.1 64-bit 9 calculations Reviewed

Tests bin/test-ifc43.mjs

SI value of the file's units

64-bit

The factor that turns the file's length, area, volume and mass values into meters, square meters, cubic meters and kilograms.

SI unit: factor = prefix; conversion-based unit: the file's factor; gram: × 0.001

For an SI area or volume unit with a prefix the factor is the prefix value itself: MILLI gives 0.001 for a square or cubic unit, where the square of a millimeter is 0.000001 m² and its cube 0.000000001 m³. Units without a prefix and conversion-based units with a stated factor are not affected.

  • ifc/ifc43.js
  • lines 66 to 88
  • BimCraft 0.8.1
function unitScales(g) {
  // SI per one file unit for each measure: the unit assignment's SI prefix or conversion factor; the defaults are the SI units
  const out = { length: g.lengthUnit, area: 1, volume: 1, mass: 1 };
  const f = g.f;
  const proj = f.ofType('IFCPROJECT', true)[0];
  const ua = proj !== undefined ? ref(g.a(proj, 'UnitsInContext')) : (f.ofType('IFCUNITASSIGNMENT')[0] ?? null);
  if (ua === null || ua === undefined) return out;
  for (const u of refs(g.a(ua, 'Units'))) {
    const kind = enm(g.a(u, 'UnitType'));
    const key = kind === 'AREAUNIT' ? 'area' : kind === 'VOLUMEUNIT' ? 'volume' : kind === 'MASSUNIT' ? 'mass' : null;
    if (!key) continue;
    const t = f.type(u);
    if (t === 'IFCSIUNIT') {
      const p = enm(g.a(u, 'Prefix'));
      out[key] = p ? PREFIX[p] ?? 1 : 1;
      if (key === 'mass' && String(enm(g.a(u, 'Name'))) === 'GRAM') out.mass *= 1e-3;
    } else {
      const v = g.unitFactor(u, 'METRE');
      if (v) out[key] = v;
    }
  }
  return out;
}

Quantities of an element

64-bit

The lengths, areas, volumes and weights an element states in its quantity sets, in SI units.

SI value = stated value × unit factor

Each stated value is multiplied once by its measure's factor in 64-bit; values that are not finite are skipped. Counts and times are passed on unchanged.

  • ifc/ifc43.js
  • lines 154 to 170
  • BimCraft 0.8.1
const quantitiesOf = (id) => {
  const q = {};
  for (const rel of psetsOf.get(id) || []) {
    const def = ref(g.a(rel, 'RelatingPropertyDefinition'));
    if (def === null || file.type(def) !== 'IFCELEMENTQUANTITY') continue;
    const set = str(g.a(def, 'Name')) || 'Quantities';
    for (const qid of refs(g.a(def, 'Quantities'))) {
      const t = file.type(qid), k = KIND_OF[t];
      if (!k) continue;
      const v = num(g.a(qid, k[1]));
      if (v === null || !Number.isFinite(v)) continue;
      const si = k[0] === 'length' ? v * un.length : k[0] === 'area' ? v * un.area : k[0] === 'volume' ? v * un.volume : k[0] === 'mass' ? v * un.mass : v;
      q[set + '.' + (str(g.a(qid, 'Name')) || k[0])] = { kind: k[0], value: si };
    }
  }
  return q;
};

Length of an alignment

64-bit

The length of an alignment curve from the lengths of its segments.

L = Σ |SegmentLength|

The absolute values of the file's stated segment lengths are added in order; the curve itself is not measured. A curve with a segment of another kind gives no length.

  • ifc/ifc43.js
  • lines 256 to 270
  • BimCraft 0.8.1
/** Length of an alignment curve in file units (sum of its curve segments' lengths), or null. */
function segmentLength(g, curve) {
  const f = g.f;
  const t = f.type(curve);
  if (t === 'IFCCOMPOSITECURVE' || t === 'IFCGRADIENTCURVE' || t === 'IFCSEGMENTEDREFERENCECURVE') {
    let s = 0, any = false;
    for (const seg of refs(g.a(curve, 'Segments'))) {
      if (f.type(seg) !== 'IFCCURVESEGMENT') return null;
      const l = num(g.a(seg, 'SegmentLength'));
      if (l !== null) { s += Math.abs(l); any = true; }
    }
    return any ? s : null;
  }
  return null;
}

Rounding of totals

64-bit

Rounds a total length, area or volume before it is listed or written to a quantities file.

ROUND(v) = v to 12 significant digits

toPrecision(12) and back to a number: the total keeps 12 significant digits, three fewer than a 64-bit number can hold. It is applied to the sums, not to the terms.

  • ifc/ifc43.js
  • line 272
  • BimCraft 0.8.1
const ROUND = (v) => Number(Number(v).toPrecision(12));

Totals per class

64-bit

The count and the total length, area and volume of each class and predefined type.

total = Σ of the first stated quantity of that kind per element

For each element the first quantity whose name is on the list for that kind is taken, and the values are added in element order in 64-bit. Elements that state none add nothing and are counted separately.

  • ifc/ifc43.js
  • lines 274 to 292
  • BimCraft 0.8.1
/** Totals per class and predefined type (SI: m, m², m³; a quantity is summed over the elements that have it, `counted` says how many). */
export function summary(r) {
  const rows = new Map();
  for (const e of r.elements) {
    const key = e.cls + '|' + (e.predefined || '');
    let row = rows.get(key);
    if (!row) rows.set(key, (row = { group: e.group, cls: e.cls, predefined: e.predefined || '', count: 0, length: 0, area: 0, volume: 0, counted: { length: 0, area: 0, volume: 0 } }));
    row.count++;
    const pick = (kind, names) => {
      for (const n of names) for (const [k, q] of Object.entries(e.quantities)) if (q.kind === kind && k.endsWith('.' + n)) return q.value;
      return null;
    };
    for (const [kind, names] of [['length', LENGTH_NAMES], ['area', AREA_NAMES], ['volume', VOLUME_NAMES]]) {
      const v = pick(kind, names);
      if (v !== null) { row[kind] += v; row.counted[kind]++; }
    }
  }
  return [...rows.values()].map((x) => ({ ...x, length: ROUND(x.length), area: ROUND(x.area), volume: ROUND(x.volume) })).sort((a, b) => a.group.localeCompare(b.group) || a.cls.localeCompare(b.cls) || a.predefined.localeCompare(b.predefined));
}

Numbers in a written file

64-bit

Formats a coordinate, length or quantity for a new IFC 4.3 file.

R(v, d) = v with d decimals, trailing zeros removed; d = 9, or 12 for directions

toFixed with 9 decimals: the file is in meters, so coordinates and lengths are written to a nanometer and areas and volumes to 9 decimals of their unit. Direction components have 12 decimals.

  • ifc/ifc43.js
  • line 317
  • BimCraft 0.8.1
const R = (v, d = 9) => { const s = Number(v).toFixed(d).replace(/0+$/, ''); return /\.$/.test(s) ? s + '0' : s.includes('.') ? s : s + '.0'; };

Alignment segments written

64-bit

The straight segments of a written alignment: each one's start, direction and length.

length = √(Δx² + Δy²); direction = (Δx, Δy) ÷ length

Each segment's length is one Math.hypot and its direction the differences divided by it; both are then written through the number formatter. The alignment's total used for the station check is the 64-bit sum of the unrounded lengths.

  • ifc/ifc43.js
  • lines 407 to 413
  • BimCraft 0.8.1
const dx = pts[i][0] - pts[i - 1][0], dy = pts[i][1] - pts[i - 1][1], len = Math.hypot(dx, dy);
if (!(len > 0)) throw new Error('Two points of an alignment coincide.');
const line = w.ent('IfcLine', { Pnt: w.pt([0, 0]), Dir: w.ent('IfcVector', { Orientation: w.dir([1, 0]), Magnitude: '1.' }) });
segs.push(w.ent('IfcCurveSegment', { Transition: i === pts.length - 1 ? '.DISCONTINUOUS.' : '.CONTSAMEGRADIENTSAMECURVATURE.',
  Placement: w.ent('IfcAxis2Placement2D', { Location: w.pt([pts[i - 1][0], pts[i - 1][1]]), RefDirection: w.dir([dx / len, dy / len]) }),
  SegmentStart: 'IFCLENGTHMEASURE(0.)', SegmentLength: 'IFCLENGTHMEASURE(' + R(len) + ')', ParentCurve: line }));
at += len;

Area of a written profile

64-bit

The cross-section area of an element in a written file, from its rectangle, circle or polygon.

circle: π r²; polygon: |Σ (xₐ y_b − x_b yₐ)| ÷ 2; rectangle: w × d

Closed forms in 64-bit with Math.PI; the polygon uses the shoelace sum in the profile's own coordinates. The written volume is this area times the height.

  • ifc/ifc43.js
  • lines 497 to 502
  • BimCraft 0.8.1
function profileArea(p) {
  if (!p) return 0;
  if (p.r) return Math.PI * p.r * p.r;
  if (p.poly) { let s = 0; for (let i = 0; i < p.poly.length; i++) { const a = p.poly[i], b = p.poly[(i + 1) % p.poly.length]; s += a[0] * b[1] - b[0] * a[1]; } return Math.abs(s) / 2; }
  return (p.w || 0) * (p.d || 0);
}

Quantities written for an element

64-bit

The length, width, height, footprint area and volume written to an element's quantity set.

Length = longer side; Width = shorter side; GrossVolume = area × height

The values are computed in 64-bit from the profile and height, may be replaced by values the caller gives, and are written with 9 decimals. For a circle the length and width are both the diameter.

  • ifc/ifc43.js
  • lines 473 to 477
  • BimCraft 0.8.1
const q = { Length: Math.max(...dims), Width: Math.min(...dims), Height: h, GrossFootprintArea: area, GrossVolume: area * h, ...(el.qto || {}) };
const qs = Object.entries(q).filter(([, v]) => Number.isFinite(v)).map(([k, v]) => {
  const area_ = /Area$/.test(k), vol = /Volume$/.test(k);
  return w.ent(area_ ? 'IfcQuantityArea' : vol ? 'IfcQuantityVolume' : 'IfcQuantityLength', { Name: S(k), [area_ ? 'AreaValue' : vol ? 'VolumeValue' : 'LengthValue']: R(v) });
});

In detail

Method
Unit factor per measure from the unit assignment; stated quantities summed; alignment length as the sum of its segment lengths; written numbers through toFixed.
Number types
64-bit numbers; decimal text in the written file.
Tolerances
A station may exceed the alignment's length by 1e-9 when a file is written. No other comparisons.
Limits
A prefixed SI area or volume unit is scaled by the prefix itself, not by its square or cube. Totals keep 12 significant digits, fewer than the 15 a 64-bit number holds. Only stated quantities are read; nothing is measured from the geometry here.

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