Resources Numerical accuracy Calculation policy

Overhead line checks: clearances, support loads and sizing

Checks an overhead line span by span and support by support: clearance to the ground and to obstacles, phase spacing, insulator swing, the loads on each support, its size, foundation and fittings.

How the precision is kept

Everything is converted to meters with 0.3048 per project foot and computed in 64-bit. The clearance along a span is the smallest of samples taken about every 2 m, so a ground high point between samples is not seen. Loads follow closed formulas given in the file header, with rule-of-thumb defaults for embedment and foundation size. Plants from the landscape arrive converted with the survey foot and are converted back with the international foot.

Open item. Use one foot for the landscape obstacles and the line (landscape-obstacles.js divides by 1200 ÷ 3937 and checks.js multiplies by 0.3048).

Calculations under this policy

17 calculations in 4 source files. 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.

powerline/model.js CivilCraft 0.8.1 64-bit 3 calculations Reviewed

Tests bin/test-powerline.mjs

Line angle at each support

64-bit

The turn of the line at each support, in degrees.

turn = |atan2 of the span ahead − atan2 of the span behind|, wrapped to 0 … 180°

A difference of two Math.atan2 bearings, wrapped by adding or subtracting 2π, then multiplied by 180 ÷ Math.PI. For a nearly straight line the two bearings are nearly equal and the small turn is their difference.

  • powerline/model.js
  • lines 162 to 172
  • CivilCraft 0.8.1
export function turnAngles(line) {
  const P = line.supports;
  return P.map((p, i) => {
    if (i === 0 || i === P.length - 1) return 0;
    const a = Math.atan2(p.y - P[i - 1].y, p.x - P[i - 1].x), b = Math.atan2(P[i + 1].y - p.y, P[i + 1].x - p.x);
    let d = b - a;
    while (d > Math.PI) d -= 2 * Math.PI;
    while (d < -Math.PI) d += 2 * Math.PI;
    return Math.abs(d) * 180 / Math.PI;
  });
}

Suspension, angle or dead-end

64-bit

Decides each support's role from the line angle when none is set.

dead-end when turn ≥ dead-end angle; angle when turn > suspension angle

The turn is compared with the rule set's two angles with no tolerance, so a turn equal to a limit within rounding can fall either way.

  • powerline/model.js
  • lines 174 to 183
  • CivilCraft 0.8.1
export function supportRoles(line, rules) {
  const ang = turnAngles(line), n = line.supports.length;
  return line.supports.map((p, i) => {
    if (p.role && p.role !== 'auto') return p.role;
    if (i === 0 || i === n - 1) return 'deadend';
    if (ang[i] >= (rules?.angleDeadEnd_deg ?? 60)) return 'deadend';
    if (ang[i] > (rules?.angleSusp_deg ?? 5)) return 'angle';
    return 'suspension';
  });
}

Chainage of the supports

64-bit

The cumulative horizontal distance along the line at each support, in project units.

ch = Σ √(Δx² + Δy²)

Math.hypot per span, added in order along the line.

  • powerline/model.js
  • lines 192 to 195
  • CivilCraft 0.8.1
export function chainage(line) {
  let c = 0;
  return line.supports.map((p, i) => { if (i) c += Math.hypot(p.x - line.supports[i - 1].x, p.y - line.supports[i - 1].y); return c; });
}
powerline/checks.js CivilCraft 0.8.1 64-bit 10 calculations Reviewed

Tests bin/test-powerline.mjs, bin/test-powerline-net.mjs, bin/test-powerline-out.mjs, bin/test-powerline-don.mjs

Attachment height

64-bit

The elevation at which the conductor is attached at each support, in meters.

attach = ground × m per unit + height above ground − fitting drop − insulator length; embedment = height ÷ 10 + 0.5 when the item gives none

A height typed on the support is taken as the height above ground; otherwise the item's length less its embedment. The ground elevation is multiplied by 0.3048 in a feet project. The insulator length is subtracted on suspension supports only.

  • powerline/checks.js
  • lines 69 to 74
  • CivilCraft 0.8.1
const H_m = p.h != null ? p.h * um : item?.height_m ?? null;
const embed = item?.embed_m ?? (H_m != null ? H_m / 10 + 0.5 : 0);
const above = H_m != null ? H_m - (p.h != null ? 0 : embed) : null;   // a height typed on the support is the height above ground
const insL = roles[i] === 'suspension' && ins ? ins.length_m || 0 : 0;
const attach = g != null && above != null ? g * um + above - fitDrop - insL : null;
const massN = eq.reduce((t, e) => t + (e.mass_kg || 0) * G, 0);

Span length and level difference

64-bit

The horizontal length of each span and the difference in attachment height between its ends.

L = √(Δx² + Δy²) × m per unit; dz = attach right − attach left

Math.hypot in project units, then one multiplication to meters. The level difference is taken as 0 when either attachment is unknown.

  • powerline/checks.js
  • lines 87 to 89
  • CivilCraft 0.8.1
const L = Math.hypot(b.x - a.x, b.y - a.y) * um;
const dz = sup[i].attach != null && sup[i + 1].attach != null ? sup[i + 1].attach - sup[i].attach : 0;
spans.push({ L, dz, i });

Clearance to the ground along a span

64-bit

The lowest height of the conductor above the ground in each load case, and where it is.

clearance = min over samples of (attach + y(x) − ground); samples = max(20, ⌈L ÷ 2⌉) + 1

A sampled minimum: the ground and the catenary are compared at evenly spaced points, about every 2 m or closer, so a ridge between two samples is missed. The check passes within 1e-6 m of the required clearance.

  • powerline/checks.js
  • lines 109 to 127
  • CivilCraft 0.8.1
const N = Math.max(20, Math.ceil(span.L_m / 2));
for (const lc of cases) {
  const cat = S.solve.cases[lc.id].spans[k];
  let min = Infinity, at = 0;
  for (let j = 0; j <= N; j++) {
    const t = j / N, x = t * span.L_m;
    const gz = groundAt(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
    if (gz == null) continue;
    const cz = sup[i].attach + cat.yAt(x);
    const cl = cz - gz * um;
    if (cl < min) { min = cl; at = x; }
  }
  if (min < Infinity) {
    span.clear[lc.id] = { min_m: min, at_m: at, need_m: lineNeed, ok: min >= lineNeed - 1e-6 };
    const margin = min - lineNeed;
    if (!span.worst || margin < span.worst.margin_m) span.worst = { caseId: lc.id, margin_m: margin };
  }
}
if (span.worst && span.worst.margin_m < -1e-6) issues.push({ code: 'clearance', sev: 'error', span: i, vals: { span: i + 1, caseId: span.worst.caseId, have: lineNeed + span.worst.margin_m, need: lineNeed } });

Clearance over an obstacle

64-bit

The height of the conductor above each obstacle the span crosses in plan, in the worst load case.

clear = min over cases of (attach + y(t × L)) − top; top = the obstacle's own top, or ground + height

The conductor is evaluated exactly at the plan crossing from the catenary, not sampled. Only the vertical distance at the crossing is measured. The check passes within 1e-6 m.

  • powerline/checks.js
  • lines 131 to 140
  • CivilCraft 0.8.1
const hits = crossings(a, b, o.pts);
let bad = null;   // one problem per obstacle and span: its lowest clearance
for (const t of hits) {
  const x = t * span.L_m, gz = o.ground != null ? o.ground : groundAt(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
  if (gz == null && o.top == null) continue;
  const top = (o.top != null ? o.top : gz + (o.h || 0)) * um, need = clearNeed(o.clearance);
  let worst = Infinity, wc = null;
  for (const lc of cases) { const cz = sup[i].attach + S.solve.cases[lc.id].spans[k].yAt(x); if (cz - top < worst) { worst = cz - top; wc = lc.id; } }
  const hit = { id: o.id, name: o.name, kind: o.kind || null, source: o.source || null, top: top / um, at_m: x, clear_m: worst, need_m: need, caseId: wc, ok: worst >= need - 1e-6 };
  span.obstacles.push(hit);

Phase spacing needed

64-bit

The spacing between phases a span needs, from its largest sag.

need = k·√(sag + insulator length) + U ÷ 150 (U in kV)

One 64-bit expression; the square root's argument is floored at 0. The file header calls it a common form with k from the rule set. The check passes within 1e-6 m.

  • powerline/checks.js
  • lines 149 to 151
  • CivilCraft 0.8.1
const f = S.solve.cases[hot].spans[k].sag, l = Math.max(sup[i].insL, sup[i + 1].insL);
const need = (R.spacingK || 0) * Math.sqrt(Math.max(0, f + l)) + kV / 150;
span.spacing = { need_m: need, have_m: have, ok: have >= need - 1e-6 };

Loads on a support

64-bit

The transverse, longitudinal and vertical load on a support in each load case and the equivalent load at its nominal point.

T = n·(w_h × wind span + (H_L + H_R)·sin(turn ÷ 2)); V = n·w_v × weight span + equipment; F_eq = √(T² + L²) × attach height ÷ nominal height

The wind span is half the sum of the two span lengths and the weight span runs between the low points either side, which can be negative. At a dead-end the longitudinal load is the difference of the two horizontal tensions, a subtraction of similar numbers when both sides are strung alike. A stay takes the resultant ÷ sin of its angle.

  • powerline/checks.js
  • lines 172 to 189
  • CivilCraft 0.8.1
const windSpan = ((catL?.L ?? 0) + (catR?.L ?? 0)) / 2;
// weight span: from the left span's lowest point to the right span's lowest point
const wL = catL ? catL.L - catL.x0 : 0, wR = catR ? catR.x0 : 0;
const weightSpan = wL + wR;
const turn = (angles[i] || 0) * DEG;
const T = nCond * (L0.wh * windSpan + (HL + HR) * Math.sin(turn / 2));
let Lg;
if (s.role === 'deadend' || !left || !right) Lg = nCond * Math.abs(HL * Math.cos(turn / 2) - HR * Math.cos(turn / 2));
else Lg = lc.id === R.asymCase ? nCond * (unb.get(i) || 0) : 0;
const V = nCond * L0.wv * weightSpan + s.massN;
const Fh = Math.hypot(T, Lg);
const nomH = s.item && s.above != null ? s.above - (s.item.loadDrop_m ?? 0.25) : null;
const attachH = s.attach != null && s.groundZ != null ? s.attach - s.groundZ * um : null;
// a stay carries the horizontal resultant at its angle (45° from the vertical unless the item says): the support keeps none of it,
// and takes the stay's downward pull as extra vertical load
const stayN = s.stay ? Fh / Math.sin(((s.stay.angle_deg ?? 45) * Math.PI) / 180) : 0;
const Feq = s.stay ? 0 : nomH > 0 && attachH != null ? Fh * attachH / nomH : Fh;
out.loads[lc.id] = { T, L: Lg, V: V + (s.stay ? stayN * Math.cos(((s.stay.angle_deg ?? 45) * Math.PI) / 180) : 0), Feq, windSpan, weightSpan, stay: stayN };

Insulator swing angle

64-bit

The angle a suspension insulator swings to under the wind case.

swing = atan2(T per conductor, V per conductor + insulator weight ÷ 2)

Math.atan2 divided by Math.PI ÷ 180. The vertical term is floored at 1e-9 N, so with uplift the angle comes out close to 90°.

  • powerline/checks.js
  • lines 198 to 200
  • CivilCraft 0.8.1
const insW = (s.ins.mass_kg || 0) * G;
const sw = Math.atan2(nCond ? T / nCond : 0, Math.max(1e-9, (nCond ? V / nCond : 0) + insW / 2)) / DEG;
out.swing_deg = sw;

Support strength check

64-bit

Whether the support's nominal load covers the worst equivalent load times the size factor.

ok when worst F_eq × factor ≤ nominal × (1 + 1e−6)

The slack is relative, one part in a million of the nominal load.

  • powerline/checks.js
  • line 222
  • CivilCraft 0.8.1
out.sizeOk = out.worstFeq * f <= s.item.nominal_N * (1 + 1e-6);

Foundation size

64-bit

The depth, diameter and volume of a support's foundation hole.

depth = embedment, or height ÷ 10 + 0.5; diameter = max(0.6, foot diameter + 0.3) m; volume = π·d² ÷ 4 × depth

The dimensions are rules of thumb written into the code, not a foundation design. The volume is the full cylinder; the pole's own volume is not taken out.

  • powerline/checks.js
  • lines 229 to 231
  • CivilCraft 0.8.1
const embed = s.item.embed_m ?? s.item.height_m / 10 + 0.5;
const dia = Math.max(0.6, (s.item.footDiam_mm || 300) / 1000 + 0.3);
out.foundation = { depth_m: embed, dia_m: dia, volume_m3: Math.PI * dia * dia / 4 * embed };

Where a span crosses an obstacle in plan

64-bit

The positions along a span, from 0 to 1, where it crosses an obstacle's outline or passes a single point.

t = ((p − a) × e) ÷ (d × e); u = ((p − a) × d) ÷ (d × e)

Segments are treated as parallel when the cross product is below 1e-12 in absolute value; that threshold is in squared project units and does not scale with the lengths. t and u must lie within 0 and 1 with no tolerance, and crossings closer than 1e-9 in t are merged.

  • powerline/checks.js
  • lines 248 to 266
  • CivilCraft 0.8.1
/** Where the plan segment a→b crosses the polyline pts (or passes a single point within 0.5 % of its length): → [t in 0..1]. */
export function crossings(a, b, pts) {
  const out = [];
  const dx = b.x - a.x, dy = b.y - a.y, L2 = dx * dx + dy * dy;
  if (!(L2 > 0)) return out;
  if (pts.length === 1) {
    const p = pts[0], t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / L2;
    if (t > 0 && t < 1 && Math.abs((p.x - a.x) * dy - (p.y - a.y) * dx) / Math.sqrt(L2) <= Math.sqrt(L2) * 0.005 + 1e-9) out.push(t);
    return out;
  }
  for (let j = 0; j < pts.length - 1; j++) {
    const p = pts[j], q = pts[j + 1], ex = q.x - p.x, ey = q.y - p.y;
    const den = dx * ey - dy * ex;
    if (Math.abs(den) < 1e-12) continue;
    const t = ((p.x - a.x) * ey - (p.y - a.y) * ex) / den, u = ((p.x - a.x) * dy - (p.y - a.y) * dx) / den;
    if (t >= 0 && t <= 1 && u >= 0 && u <= 1 && !out.some((o) => Math.abs(o - t) < 1e-9)) out.push(t);   // a shared vertex: once
  }
  return out;
}
powerline/landscape-obstacles.js CivilCraft 0.8.1 64-bit 3 calculations Reviewed

Tests bin/test-powerline.mjs

Plant sizes in project units

64-bit

The factor that turns a plant's mature height and spread in meters into project units.

project units per meter = 1 ÷ (1200 ÷ 3937) for feet

Plant sizes are divided by the US survey foot here. checks.js then multiplies heights by 0.3048, so in a feet project an obstacle's height comes back about 2 parts in a million smaller than the plant's size in meters.

  • powerline/landscape-obstacles.js
  • line 45
  • CivilCraft 0.8.1
const perM = 1 / (units === 'm' ? 1 : FT_M);   // project units per metre (plant sizes are metres)

Tree canopy as a ring

64-bit

The outline of a plant's canopy used to find where a span passes over it.

12 points on the circle of radius r: (x + r·cos(2πk ÷ 12), y + r·sin(2πk ÷ 12))

The straight sides lie inside the true circle by up to r × (1 − cos 15°), about 3.4 percent of the radius, so a span that only clips the canopy's edge between two points is not counted as crossing it.

  • powerline/landscape-obstacles.js
  • lines 34 to 38
  • CivilCraft 0.8.1
function ring(x, y, r, n = 12) {
  const pts = [];
  for (let k = 0; k <= n; k++) { const a = (k % n) / n * 2 * Math.PI; pts.push({ x: x + r * Math.cos(a), y: y + r * Math.sin(a) }); }
  return pts;
}

A plant as an obstacle

64-bit

A single plant's canopy radius, height and ground level as an obstacle under a line.

radius = max(spread ÷ 2, 0.1); height = mature height

The radius is floored at 0.1 project units. The ground is the plant's own elevation when it has one, else the surface at its position.

  • powerline/landscape-obstacles.js
  • line 54
  • CivilCraft 0.8.1
push({ id: 'ls:plant:' + p.id, kind: 'plant', name: label(sp), pts: ring(p.x, p.y, Math.max(m.s * perM / 2, 0.1)), h: m.h * perM, ground: fin(p.z) ? p.z : g(p.x, p.y), top: null, clearance: trees });
powerline/powerline-ui.js CivilCraft 0.8.1 64-bit 1 calculation Reviewed

Tests None found by the review

Foundation volume shown in cubic yards

64-bit

The foundation volume in the support's properties, in cubic yards for a feet project.

yd³ = m³ × 1.307951

The factor is written to 7 digits here, while outputs.js converts volumes with the exact 0.764554857984 m³ per cubic yard. The value is shown to 2 decimals, so the difference does not reach the display.

  • powerline/powerline-ui.js
  • line 496
  • CivilCraft 0.8.1
[t('pl.p.found'), sr.foundation ? t('pl.p.found.v', { d: lenText(sr.foundation.depth_m / um()), dia: lenText(sr.foundation.dia_m / um()), v: U() === 'm' ? fixed(sr.foundation.volume_m3, 2) + ' m³' : fixed(sr.foundation.volume_m3 * 1.307951, 2) + ' yd³' }) : '']]) : '',

In detail

Method
Wind span and weight span from the catenaries either side; transverse load from wind and the line angle; equivalent load at the support's nominal point by the ratio of heights; phase spacing k·√(sag + insulator length) + U ÷ 150; swing angle from atan of transverse over vertical, as listed in the file header.
Number types
64-bit numbers.
Tolerances
Clearance, obstacle and spacing checks pass within 1e-6 m, absolute. Support strength passes within a relative 1e-6. Parallel test in plan crossings: |cross product| below 1e-12, absolute. A single-point obstacle counts within 0.5 percent of the span length plus 1e-9.
Limits
Clearance is sampled at max(20, ⌈L ÷ 2⌉) intervals per span. A tree canopy is a 12-sided ring and only vertical clearance where the span crosses it in plan is checked. Embedment defaults to height ÷ 10 + 0.5 m; foundation diameter to max(0.6, foot diameter + 0.3) m.

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