Grades and vertical curves
64-bitBuilds the grades between PVIs and the curve at each interior PVI.
g = Δz ÷ Δsta; A = (g₂ − g₁) × 100; L = K × |A|; z_BVC = z_PVI − g₁ × L ÷ 2; x_high/low = −g₁ × L ÷ (g₂ − g₁)
Grades divide by the station difference; two PVIs less than 1e-6 apart are reported, but the division is still done. A curve is skipped when L ≤ 0 or |A| < 1e-9. The high or low point is computed only when the grades have opposite signs, so its divisor cannot be zero. Overlap tests use 1e-9, absolute.
export function buildProfile(pvisIn) {
const errors = [];
const pvis = (pvisIn || []).filter((p) => Number.isFinite(p.sta) && Number.isFinite(p.z)).map((p) => ({ ...p })).sort((a, b) => a.sta - b.sta);
for (let i = 1; i < pvis.length; i++) if (pvis[i].sta - pvis[i - 1].sta < 1e-6) errors.push(t('prof.err.two', { sta: pvis[i].sta }));
const grades = [];
for (let i = 0; i + 1 < pvis.length; i++) grades.push({ from: pvis[i].sta, to: pvis[i + 1].sta, g: (pvis[i + 1].z - pvis[i].z) / (pvis[i + 1].sta - pvis[i].sta) });
const curves = [];
for (let i = 1; i + 1 < pvis.length; i++) {
const v = pvis[i], g1 = grades[i - 1].g, g2 = grades[i].g, A = (g2 - g1) * 100;
let L = Number(v.L) || 0;
if (!L && Number(v.K) > 0) L = Number(v.K) * Math.abs(A);
v.L = L;
if (L <= 0 || Math.abs(A) < 1e-9) continue;
const bvc = v.sta - L / 2, evc = v.sta + L / 2;
const c = { i, pvi: v.sta, zPvi: v.z, bvc, evc, L, g1, g2, A, K: L / Math.abs(A), zBvc: v.z - g1 * L / 2, zEvc: v.z + g2 * L / 2, kind: g2 < g1 ? 'crest' : 'sag' };
if (g1 * g2 < 0) {
const x = -g1 * L / (g2 - g1);
const pt = { sta: bvc + x, z: c.zBvc + g1 * x + (g2 - g1) * x * x / (2 * L) };
if (c.kind === 'crest') c.high = pt; else c.low = pt;
}
curves.push(c);
}
// curves must stay between their neighbours and not overlap
for (let k = 0; k < curves.length; k++) {
const c = curves[k], prevEnd = k ? curves[k - 1].evc : pvis[0].sta;
const left = pvis[c.i - 1].sta, right = pvis[c.i + 1].sta;
if (c.bvc < left - 1e-9 || c.bvc < prevEnd - 1e-9) errors.push(t('prof.err.before', { pvi: c.i + 1, len: c.L.toFixed(2) }));
if (c.evc > right + 1e-9) errors.push(t('prof.err.past', { pvi: c.i + 1, len: c.L.toFixed(2) }));
}
return new Profile(pvis, grades, curves, errors);
}