Works out free-space loss, Fresnel zone clearance over the terrain, link margin, fiber loss, link availability, and the wind forces and leg loads of a tower.
How the precision is kept
Each formula is one 64-bit expression. The accuracy is set by the published engineering forms and their rounded constants: 32.44 in the path loss, 17.32 in the Fresnel radius and 12.74 in the earth bulge. The speed of light is exact. Clearance is tested at the terrain samples only, and the mast height search steps by 0.5 m.
Open item. Compute the three rounded constants (32.44, 17.32, 12.74) from the speed of light already in the file and a stated earth radius.
Calculations under this policy
13 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.
The loss in decibels between two antennas in free space.
FSPL = 20·log₁₀(d km) + 20·log₁₀(f MHz) + 32.44
The constant 32.44 is typed to 2 decimals; 20·log₁₀(4π × 10⁹ ÷ 299792458) is 32.4478, so every loss is 0.008 dB lower than the exact form gives. A distance of zero gives minus infinity; the callers pass a positive distance.
The clearance of the straight line between the antennas over the terrain at every sample, and its ratio to the Fresnel radius.
line = z_A + (z_B − z_A)·d₁ ÷ D; clearance = line − (terrain + clutter + bulge); ratio = clearance ÷ F₁
64-bit at each terrain sample. The tightest point is the sample with the lowest ratio, so an obstruction between samples is not seen. The end points are left out of the ratio, which keeps F₁ from being zero in the division.
telecom.js
lines 39 to 54
CivilCraft 0.8.1
/**
* terrain: samples from A to B { d (km), z (m above datum) }. Antennas at A and B stand hA and hB above their ground. The line of sight is a straight line between the antenna
* tops; the terrain is raised by the earth bulge (and `clutter` metres for trees or buildings). Clearance = line − (terrain + bulge); the ratio to F1 is what a planner reads.
*/
export function losProfile(terrain, { hA, hB, f, k = 4 / 3, clutter = 0 }) {
const D = terrain[terrain.length - 1].d - terrain[0].d, zA = terrain[0].z + hA, zB = terrain[terrain.length - 1].z + hB, samples = [];
let min = null;
for (const p of terrain) {
const d1 = p.d - terrain[0].d, d2 = D - d1, line = zA + ((zB - zA) * d1) / D;
const obstruction = p.z + clutter + (d1 > 0 && d2 > 0 ? earthBulge(d1, d2, k) : 0), clearance = line - obstruction;
const F1 = d1 > 0 && d2 > 0 ? fresnel(d1, d2, f) : 0, ratio = F1 > 0 ? clearance / F1 : Infinity;
samples.push({ d: p.d, line, obstruction, clearance, F1, ratio });
if (d1 > 0 && d2 > 0 && (!min || ratio < min.ratio)) min = { d: p.d, clearance, ratio, F1 };
}
return { clear: !!min && min.clearance >= 0.6 * min.F1, min, samples, length: D };
}
The lowest tower height at one end that clears a set fraction of the Fresnel zone along the path.
first h = h₀ + 0.5·j with clearance ≥ fraction × F₁
A search by repeated addition of 0.5 m up to 300 m, so the answer is a multiple of 0.5 m above the start height, not the exact height. 0.5 is exact in binary, so the additions do not drift.
telecom.js
lines 61 to 65
CivilCraft 0.8.1
/** The tower height at A that makes the path clear `fraction` of F1 everywhere (a plain search up to 300 m in 0.5 m steps). */
export function mastHeightFor(terrain, { hB, f, k = 4 / 3, fraction = 0.6, clutter = 0, max = 300, hA0 = 2 }) {
for (let h = hA0; h <= max; h += 0.5) { const r = losProfile(terrain, { hA: h, hB, f, k, clutter }); if (r.min && r.min.clearance >= fraction * r.min.F1) return h; }
return null;
}
Fiber loss budget
64-bit
The loss of a fiber run and the longest run a power budget allows.
loss = L·α + splices·Ls + connectors·Lc + margin; max L = (budget − connectors·Lc − margin − splices·Ls) ÷ α
64-bit sums of products; the longest run is only computed when the attenuation is above zero and is floored at zero.
An empirical formula evaluated in 64-bit and capped at 1. Its coefficients and the terrain factors 4, 1 and 0.25 are the published round numbers.
telecom.js
lines 95 to 98
CivilCraft 0.8.1
export function availability({ dKm, fGHz, fadeMargin, terrain = 'average' }) {
const C = typeof terrain === 'number' ? terrain : TERRAIN_C[terrain] ?? 1, core = C * fGHz * dKm ** 3 * Math.pow(10, -fadeMargin / 10), wm = Math.min(1, 2.5e-6 * core), an = Math.min(1, 6e-7 * core);
return { worstMonth: wm, annual: an, secondsPerYear: an * 365.25 * 86400, C };
}
Tower base shear, moment and overturning
64-bit
The wind on the tower shaft and its antennas, and the safety factor of a square footing against overturning.
shaft: Σ over 10 slices of q·Cf·solidity·width·Δz at mid-height; FS = W·1000·footing ÷ 2 ÷ M
The shaft is summed over 10 equal slices at their mid-heights, which is exact for the uniform pressure used here. The safety factor is only computed when moment, footing and weight are all above zero.
telecom.js
lines 101 to 108
CivilCraft 0.8.1
/** Wind on the tower's shaft plus its antennas, and the overturning of a square footing (side `footing`, m, and the weight in kN). */
export function towerBase({ height, width, solidity = 0.3, cf = 2.5, antennas = [], v = 40, rho = 1.225, weight = 0, footing = 0 }) {
const q = 0.5 * rho * v * v, n = 10, dz = height / n; let sf = 0, sm = 0;
for (let i = 0; i < n; i++) { const F = q * cf * solidity * width * dz, z = (i + 0.5) * dz; sf += F; sm += F * z; }
const a = windLoad(antennas, { v, rho });
const moment = sm + a.moment, shear = sf + a.shear;
return { q, shaft: { force: sf, moment: sm }, antennas: { force: a.shear, moment: a.moment }, shear, moment, vertical: weight, fsOverturn: moment > 0 && footing > 0 && weight > 0 ? (weight * 1000 * footing / 2) / moment : null };
}
Knife-edge diffraction loss
64-bit
The extra loss when one obstruction rises into the path.
v = h·√(2(d₁ + d₂) ÷ (λ·d₁·d₂)); J(v) = 6.9 + 20·log₁₀(√((v − 0.1)² + 1) + v − 0.1) for v > −0.78, else 0
The closed approximation the file attributes to ITU-R P.526, in 64-bit. At the cutoff v = −0.78 the formula gives about 0 dB, and below it the loss is set to 0; inside the range the argument of the logarithm is positive.
telecom.js
line 117
CivilCraft 0.8.1
export function knifeEdgeLoss(v) { return v > -0.78 ? 6.9 + 20 * Math.log10(Math.sqrt((v - 0.1) ** 2 + 1) + v - 0.1) : 0; }
Tower leg forces and buckling
64-bit
The compression and tension in a tower leg from the wind moment and the weight, and the leg's Euler buckling load.
64-bit closed forms with Math.PI and Math.sqrt(3). Unit factors are powers of ten (GPa to Pa, cm⁴ to m⁴, N to kN). Divisions are guarded by tests that the width, inertia and length are above zero.
telecom.js
lines 148 to 162
CivilCraft 0.8.1
/**
* A lattice tower's legs under the wind moment and the dead weight, and each leg's buckling as a column. Statics only (equal legs): a tower of `legs` 4 (square, wind on a face) has
* the windward leg pair carrying M/(2·w) each; a triangular tower of 3 (wind on a face) has an apex leg M/h with h = w·√3/2 (the leg positions from the centroid, Σx² = 2h²/3).
* Dead weight W is shared by the legs. Capacity checks use the person's numbers: `legCapacity` kN (0 = unknown, no check), `loadFactor` (a code's factor: NOT typed in here), and
* Euler buckling Pcr = π²·E·I/(K·L)² with E in GPa, I in cm⁴, L unbraced length in m. Diagonal wind, torsion, bracing and joint design are not covered. A planning aid, not a code check.
* towerLegs({ legs, width, moment (N·m), vertical (kN), legCapacity (kN), loadFactor, E (GPa), I (cm4), L (m), K }) → { compression, tension, utilization, pcr, bucklingUtil } (kN)
*/
export function towerLegs({ legs = 4, width, moment, vertical = 0, legCapacity = 0, loadFactor = 1, E = 200, I = 0, L = 0, K = 1 }) {
const w = width, M = moment / 1000, W = vertical; // kN·m, kN
const lever = legs === 3 ? (w * Math.sqrt(3)) / 2 : 2 * w, n = legs === 3 ? 3 : 4;
const wind = M > 0 && w > 0 ? M / lever : 0, share = W / n;
const compression = (wind + share) * loadFactor, tension = Math.max(0, wind - share) * loadFactor;
const pcr = I > 0 && L > 0 ? (Math.PI ** 2 * E * 1e9 * I * 1e-8) / (K * L) ** 2 / 1000 : null; // kN
return { compression, tension, utilization: legCapacity > 0 ? compression / legCapacity : null, pcr, bucklingUtil: pcr ? compression / pcr : null };
}
In detail
Method
Free-space loss (the file cites ITU-R P.525), first Fresnel zone and knife-edge loss (ITU-R P.526), earth bulge with the effective earth radius factor (ITU-R P.834), Vigants-Barnett multipath outage (Freeman, Radio System Design for Telecommunications, ch. 10), rain attenuation in the form of ITU-R P.838, wind pressure ½ρV² and Euler buckling.
Number types
64-bit numbers.
Tolerances
A path is clear when the tightest clearance is at least 0.6 of the first Fresnel radius there; the mast search steps by 0.5 m up to 300 m.
Limits
The constants 32.44, 17.32 and 12.74 are rounded; computed from the exact speed of light and an earth radius of 6371 km they are 32.4478, 17.3145 and 12.742. The outage formula is empirical. The tower shaft wind is summed over 10 slices, and the tower check is statics only.
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.