Running saved geotechnical, survey, pit and link items
Converts a saved item from project units to meters, runs the engine for it (soil profile, slope stability, settlement, traverse, pit, radio link, coverage) and converts the results back.
How the precision is kept
Lengths are multiplied by 0.3048 or 1 on the way in and divided on the way out, in 64-bit. The ground and the soil layers are sampled at a fixed number of points along a section, so the engines see a sampled copy of the model. Several thresholds are fixed numbers.
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.
The factor that takes a project length to meters for every engine in this file.
feet: 0.3048; meters: 1
0.3048 is the exact international foot. units.js in the same build gives the project foot as 1200/3937 m, the US survey foot, which is larger by 2 parts per million. Results are converted back with the same factor, so a length that goes in and out is unchanged.
geo-run.js
lines 30 to 31
CivilCraft 0.8.1
export const FT = 0.3048;
export const toM = (units) => (units === 'ft' ? FT : 1);
Soil profile along a section
64-bit
The borings in meters, the layers between them, and the layer stack at evenly spaced stations.
The ground line, layer tops, water level, surcharges and floor that the slope stability engine reads.
81 samples along the section; layer top = min(layer top, ground)
The ground is read from the surface at 81 evenly spaced points and joined by straight lines, so ground features between samples are not seen. The default floor is the lower of the deepest boring and the lowest ground minus half the ground's height range.
geo-run.js
lines 44 to 64
CivilCraft 0.8.1
export function slopeGeometry(geo, slope, units, ground, { points = 80 } = {}) {
const p = runProfile(geo, slope.section, units); if (!p) return { problems: [{ code: 'geo.slope.nosection' }] };
const { profile, k } = p, L = profile.length; if (!ground) return { problems: [{ code: 'geo.slope.noground' }] };
const line = pl(p.section.line, k), pos = (d) => { let acc = 0; for (let i = 1; i < line.length; i++) { const a = line[i - 1], b = line[i], l = Math.hypot(b.x - a.x, b.y - a.y); if (d <= acc + l || i === line.length - 1) { const u = Math.max(0, Math.min(1, (d - acc) / l)); return { x: a.x + (b.x - a.x) * u, y: a.y + (b.y - a.y) * u }; } acc += l; } return line[0]; };
const gz = (d) => { const q = pos(d), v = ground.elevationAt(q.x / k, q.y / k); return v === null || v === undefined ? NaN : v * k; };
const gp = []; for (let i = 0; i <= points; i++) { const d = (L * i) / points, z = gz(d); if (Number.isFinite(z)) gp.push({ x: d, z }); }
if (gp.length < 3) return { problems: [{ code: 'geo.slope.noground' }] };
// units from the profile (named strata, top first at each station); a unit's top line = its top at every station where it exists
const units_ = []; for (const s of profile.strata) units_.push(s.unit);
const topLine = (unit) => { const pts = []; for (let i = 0; i <= points; i++) { const d = (L * i) / points, st = stackAt(profile, d).find((q) => q.unit === unit); if (st) pts.push({ x: d, z: Math.min(st.top, gp[Math.min(i, gp.length - 1)].z) }); } return pts; };
const mats = slope.materials || {}, byUnit = {};
for (const b of geo.borings || []) for (const l of b.layers) if (!byUnit[l.unit]) byUnit[l.unit] = l;
const layers = [];
const order = units_.map((u) => ({ u, line: topLine(u) })).filter((q) => q.line.length).sort((a, b) => b.line.reduce((s, q) => s + q.z, 0) / b.line.length - a.line.reduce((s, q) => s + q.z, 0) / a.line.length);
for (const { u, line: tl } of order) { const m = mats[u] || byUnit[u] || { gamma: 18, c: 0, phi: 28 }; layers.push({ name: u, top: tl.length > 1 ? tl : [{ x: 0, z: tl[0].z }, { x: L, z: tl[0].z }], gamma: m.gamma, c: m.c, phi: m.phi, ...(m.cu !== undefined ? { cu: m.cu } : {}) }); }
if (!layers.length) layers.push({ name: 'soil', top: gp.map((q) => ({ ...q })), gamma: 18, c: 5, phi: 28 });
layers[0].top = gp.map((q) => ({ ...q })); // the first layer starts at the ground
const floor = slope.floor !== null && slope.floor !== undefined ? slope.floor * k : Math.min(...profile.borings.map((b) => b.z - b.depth), Math.min(...gp.map((q) => q.z)) - 0.5 * (Math.max(...gp.map((q) => q.z)) - Math.min(...gp.map((q) => q.z))));
const geom = { ground: gp, layers, water: slope.water === null || slope.water === undefined ? null : slope.water * k, ru: slope.ru || 0, surcharge: (slope.surcharge || []).map((s) => ({ x0: s.x0 * k, x1: s.x1 * k, q: s.q })), floor };
return { geom, profile, k, problems: profile.problems || [] };
}
Slope stability run
64-bit
The factor of safety of a given circle, or the critical circle from a search.
A given circle is scaled to meters by the unit factor. The search uses a 14 by 10 grid of centers with 14 radii and 60 slices. A factor of safety below 1.5 is flagged.
Settlement under a footing at a boring, and the time for 50 % and 90 % consolidation.
layer top = (z − from) × k; drainage path = clay thickness ÷ 2
Layer elevations are the boring elevation minus the depth, times the unit factor. Sub-layers are 1 m at most. The time rate uses only the first layer that has both cv and Cc and assumes drainage at both faces. A total above 0.025 m is flagged whatever the user's limit.
A traverse closed by the compass rule from legs typed as distance and azimuth in degrees.
azimuth in radians = degrees × π ÷ 180
Azimuths are converted with the 64-bit value of π. A precision ratio below 1 : 5000 is flagged; that limit is fixed in the code.
geo-run.js
lines 116 to 121
CivilCraft 0.8.1
/** A traverse (start, legs of distance + azimuth in degrees, optionally a known end) closed by the compass rule. Lengths in the project unit. */
export function runTraverse(tv) {
const r = bowditch(tv.start, tv.legs.map((l) => ({ dist: l.dist, az: l.az * D2R })), { end: tv.end || null });
const problems = r.precision < 5000 ? [{ code: 'geo.traverse.loose', value: r.precision }] : [];
return { ...r, problems };
}
Radio link run
64-bit
Path length, terrain profile, line of sight, link budget and mast height for a link between two points.
distance in km = √(Δx² + Δy²) ÷ 1000
The path length is the plan distance in meters by hypot. Terrain is read along the path with the sample count set to 100. Heights are converted to meters going in and the mast height is divided by the factor coming out.
geo-run.js
lines 145 to 160
CivilCraft 0.8.1
export function runLink(l, units, ground) {
const k = toM(units), A = { x: l.a.x * k, y: l.a.y * k }, B = { x: l.b.x * k, y: l.b.y * k };
const g = (x, y) => { const v = ground ? ground.elevationAt(x / k, y / k) : 0; return v === null || v === undefined ? NaN : v * k; };
const terrain = terrainAlong(g, A, B, 100), dKm = Math.hypot(B.x - A.x, B.y - A.y) / 1000;
const los = losProfile(terrain, { hA: l.a.h * k, hB: l.b.h * k, f: l.f, k: l.k, clutter: l.clutter * k });
const budget = linkBudget({ pt: l.pt, gt: l.gt, gr: l.gr, dKm, fMHz: l.f * 1000, sensitivity: l.sensitivity });
const mast = los.clear ? null : mastHeightFor(terrain, { hB: l.b.h * k, f: l.f, k: l.k, clutter: l.clutter * k });
const avail = availability({ dKm, fGHz: l.f, fadeMargin: budget.margin, terrain: l.terrain }), tower = l.towerWidth > 0 ? towerBase({ height: l.a.h * k, width: l.towerWidth * k, solidity: l.towerSolidity, antennas: [{ area: l.antennaArea * k * k, h: l.a.h * k, cd: 1.2 }], v: l.wind, weight: l.towerWeight, footing: l.footing * k }) : null;
const problems = []; if (!los.clear) problems.push({ code: 'tel.los.blocked', clearance: los.min?.clearance, ratio: los.min?.ratio });
if (budget.margin < 10) problems.push({ code: 'tel.margin.low', margin: budget.margin });
// 0.8 EG.28: the legs under that wind moment, against the person's capacity and factor (no code's numbers are built in)
const legs = tower ? towerLegs({ legs: l.towerLegs, width: l.towerWidth * k, moment: tower.moment, vertical: l.towerWeight, legCapacity: l.legCapacity, loadFactor: l.loadFactor, E: l.legE, I: l.legI, L: l.legLength * k, K: l.legK }) : null;
if (legs?.utilization > 1) problems.push({ code: 'tel.leg.over', util: legs.utilization });
if (legs?.bucklingUtil > 1) problems.push({ code: 'tel.leg.buckling', util: legs.bucklingUtil });
return { terrain, los, budget, mast: mast === null ? null : mast / k, dKm, fspl: fspl(dKm, l.f * 1000), availability: avail, tower, legs, problems };
}
Coverage run
64-bit
Received power over a grid round a transmitter and the farthest covered cell.
farthest = max over covered cells of √(Δx² + Δy²) ÷ k
The farthest distance is taken at cell positions, so it is known to the grid step. The number of cells across is a floor of range ÷ step.
geo-run.js
lines 162 to 172
CivilCraft 0.8.1
/** A coverage sketch: the received power over the ground around a transmitter (telecom.js coverage). `ground(x, y)` in the project unit, like runLink's. */
export function runCover(c, units, ground) {
const k = toM(units), g = (x, y) => { const v = ground ? ground.elevationAt(x / k, y / k) : 0; return v === null || v === undefined ? NaN : v * k; };
const n = Math.floor(c.range / c.step);
if (n > 150) return { ok: false, problems: [{ code: 'tel.cover.fine', cells: (2 * n + 1) ** 2 }] };
const r = coverage(g, { x: c.tx.x * k, y: c.tx.y * k, h: c.tx.h * k }, { f: c.f, pt: c.pt, gt: c.gt, gr: c.gr, sensitivity: c.sensitivity, rxH: c.rxH * k, radius: c.range * k, step: c.step * k, k: c.k, clutter: c.clutter * k });
let far = 0; for (let j = 0; j < r.ny; j++) for (let i = 0; i < r.nx; i++) { const p = r.pr[i + r.nx * j]; if (p >= c.sensitivity) far = Math.max(far, Math.hypot(r.x0 + i * r.step - c.tx.x * k, r.y0 + j * r.step - c.tx.y * k)); }
const problems = [];
if (!r.cells) problems.push({ code: 'tel.cover.noground' });
return { ok: true, ...r, farthest: far / k, problems };
}
Open pit run
64-bit
The block model in meters, block values, the ultimate pit and its phases.
slope in radians = degrees × π ÷ 180; phase factors from 0.4 to 1 in equal steps
Block sizes, origin and sample positions are multiplied by the unit factor. Grades are estimated by inverse distance with power 2 and no search radius. The phase price factors are fixed in the code.
One conversion factor per run. Ground line for slope stability from 81 samples along the section. Settlement in sub-layers of at most 1 m. Traverse azimuths converted from degrees.
Number types
64-bit numbers.
Tolerances
Settlement flagged above 0.025 m. Slope flagged below a factor of safety of 1.5. Traverse flagged when perimeter ÷ misclosure is below 5000. Link margin flagged below 10 dB. Coverage refused above 150 cells of range.
Limits
Feet are converted with 0.3048 (the international foot), while units.js in the same build defines the project foot as 1200/3937 m; the two differ by 2 parts per million. Consolidation time uses the first clay layer only and takes its drainage path as half its thickness.
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.