Takes what is typed in a property box or picked in the plan and profile views and stores it in the model: lengths, coordinates, profile points, pad elevations and default curve radii.
How the precision is kept
Typed numbers are read with Number and stored unrounded. Several picked values are rounded before they are stored: profile points to 2 decimals, a new pad's elevation to 1 decimal, a typed sum of lengths to 9 decimals, and a default radius down to a whole number. A box commits its text on Enter even when the text is the rounded display value.
Open item. Commit a box only when its text was edited, so that Enter in an untouched box does not replace the stored value with its rounded display text.
Calculations under this policy
8 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 text of a property box turned into the value handed to the model.
typed sum of lengths → Number(value.toFixed(9))
A plain number is passed on as typed. A length expression such as 4+7/16 is evaluated and rounded to 9 decimals before it is committed. Enter dispatches a change event whether or not the text was edited, so the box's text is committed as it stands; where the box was filled with a rounded display value, that rounded value is what gets stored.
civilcraft.js
lines 724 to 749
CivilCraft 0.8.1
// 0.6 DF.3: every number box is a text box (a number box can't hold "4+7/16"); what is not a plain number is read as a
// length or a sum of lengths in the project's units (units.js readLength) and the box shows the result. `calc: false`
// for boxes that are not lengths (grades in %, arc radius words, point numbers and descriptions), `station: true` for
// stations ("10+00" is a station, never a sum)
function numInput(value, onCommit, { label, id, text = false, width, calc = true, station = false } = {}) {
void text; // (kept for the callers: every box is text now)
const inp = el('input', { type: 'text', className: 'cv-input', value: value ?? '', 'aria-label': label, id, inputMode: 'decimal', style: width ? 'width:' + width : undefined });
// Enter commits at once (a synthetic change); the browser's own change when the box then loses focus must not commit
// the same text again — "Raise 2" would raise twice (0.5: found by the WL.20 browser test)
let entered = null;
inp.addEventListener('change', (e) => {
if (e.isTrusted && entered !== null && inp.value === entered) { entered = null; return; }
entered = e.isTrusted ? null : inp.value;
let v = inp.value;
if (calc && !station && v.trim() !== '' && !isNum(v)) {
const r = readLength(v, units());
if (!r.ok) { inp.setAttribute('aria-invalid', 'true'); say(r.error); return; }
v = String(Number(r.value.toFixed(9)));
inp.value = v;
if (entered !== null) entered = v;
}
if (onCommit(v) === false) inp.setAttribute('aria-invalid', 'true'); else inp.removeAttribute('aria-invalid');
});
inp.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); inp.dispatchEvent(new Event('change')); } });
return inp;
}
PI coordinates box
64-bit
The easting and northing of an alignment's point of intersection as shown and as stored from the box.
The boxes are filled with the coordinate at 2 decimals. A committed box stores Number(text), so pressing Enter in an untouched box replaces the stored coordinate by its 2-decimal text.
The ground elevation at the mean of the corner coordinates, rounded to 1 decimal before it is stored. The mean adds each corner divided by the count, so it is the vertex average, not the area centroid. Off the surface the pad starts at 0.
The radius given to each bend of a newly drawn alignment.
r = min(R, ⌊half the shorter leg ÷ tan(Δ ÷ 2)⌋)
The radius that fits half of each tangent is rounded down to a whole number on purpose, as the comment says. No curve is given when the deflection is below 1e-6 rad or within 0.001 rad of a reversal.
civilcraft.js
lines 1644 to 1651
CivilCraft 0.8.1
/** The default radius, or the largest whole radius whose curve fits half of each tangent. */
function fitRadius(pts, i, R) {
const a = pts[i - 1], b = pts[i], c = pts[i + 1];
const d = Math.abs(normAng(Math.atan2(c.y - b.y, c.x - b.x) - Math.atan2(b.y - a.y, b.x - a.x)));
if (d < 1e-6 || d > Math.PI - 1e-3) return 0;
const half = Math.min(Math.hypot(b.x - a.x, b.y - a.y), Math.hypot(c.x - b.x, c.y - b.y)) / 2;
return Math.min(R, Math.floor(half / Math.tan(d / 2)));
}
Ground elevation at a station
64-bit
The existing ground elevation read out at the cursor's station in the profile view.
z = z_a + (z_b − z_a) × (s − s_a) ÷ (s_b − s_a)
Linear interpolation between the two sampled ground profile points round the station. The divisor is floored at 1e-12. The cut or fill shown beside it is the difference between the design elevation and this value.
civilcraft.js
lines 1342 to 1345
CivilCraft 0.8.1
function interpEg(eg, sta) {
for (let i = 1; i < eg.length; i++) if (sta <= eg[i].sta) { const a = eg[i - 1], b = eg[i]; if (a.z === null || b.z === null) return null; return a.z + (b.z - a.z) * (sta - a.sta) / Math.max(b.sta - a.sta, 1e-12); }
return null;
}
In detail
Method
Property boxes are text boxes. A plain number is passed on as typed; anything else is read as a length or a sum of lengths in project units and its result is put back into the box rounded to 9 decimals. Picks in the profile view are converted from screen position to station and elevation and rounded to 2 decimals.
Number types
64-bit numbers; rounding by toFixed as listed.
Tolerances
A new profile point is refused within 0.01 of an existing one, and a dragged one is kept 0.01 from its neighbors. Default radius: none when the deflection is below 1e-6 rad or within 0.001 rad of π.
Limits
Rounding to 2 decimals means 0.005 of a project unit in station and elevation for picked profile points. Boxes for PI coordinates show 2 decimals and vertex elevations 3; committing such a box unedited stores the shown text.
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.