winds-engine
Deterministic wind profiles and radiosonde validation for trajectory
simulation — the wind data pipeline for rocket modeling, consumed by
rockets-engine. Kernel-side wind modeling (the WindField trait, profile
types, analytic profiles) is staged here pending promotion to
ballistics-engine's wind module; see
docs/winds-engine-plan.md.
use ;
// A measured sounding in the line-based interchange format. Heights are
// metres above sea level; this station's surface report is at 287 m.
let sounding = parse?;
// The profile keeps those heights, so it is read with y = height above sea
// level, the frame rockets-engine uses.
let profile = sounding.to_profile_field?;
let wind = profile.wind; // 500 m above sea level
// Or score a candidate profile against the measurement.
let report = validate;
-
WindFieldtrait: stateless, deterministicwind(time, position)— the same sampling pattern trajectory integrators already rely on. -
ProfileField(piecewise-linear, circular direction interpolation),ConstantField,LogProfile,EkmanProfile. -
Sounding: radiosonde interchange format (tested against a real 115-level KMPX sounding from the IEM RAOB archive). Heights are above sea level and stay that way into_profile_field(), so the profile reads directly in a frame whose y is altitude above sea level, as in rockets-engine (withBallisticForcesand in its flat-frame CLI, whose ground sits at--ground-altitude-m, sea level by default). -
Datum conversion:
LogProfileandEkmanProfiletake y as height above the ground, where they are calm. This crate's staged types have no offset wrapper; ballistics-engine'sHeightOffset(from its next release, which rockets-engine's=0.48.0pin does not have) wraps only ballistics-engine's ownWindField, not this crate's. So a caller in a sea-level frame either reads them aty - ground elevationat its single call site, or builds ballistics-engine'sProfileField/LogProfile/EkmanProfilefrom the same values and wraps those inHeightOffset(offset = the height of the caller's y = 0 in the field's datum: minus the ground elevation for a log or Ekman profile, none for a sea-level sounding). Convert in one place only: sounding heights stay above sea level, and shifting them as well would move every wind by the site elevation. -
validate: speed/direction RMSE and per-layer errors vs a sounding. -
Relationship to ballistics-engine: these types are this crate's own. ballistics-engine's wind fields (unreleased) were adapted from them and differ:
WindField::windtakes and returnsnalgebra::Vector3<f64>and there is noWindtype,ConstantFieldis a tuple struct over that vector,LogProfile/EkmanProfilekeep their fields private behind getters, and constructors returnWindFieldError. Moving winds-engine onto them changes this crate's public API (the example above,validate()'s signature), so it is a deliberate breaking release, not a drop-in re-export. -
grib(feature): decode GRIB2 forecast files (GFS isobaric HGT/UGRD/VGRD) into aProfileFieldat a point — bilinear-interpolated, offline and deterministic. -
download(feature): fetch live forecasts — GFS via the NOAA NOMADS filter script (server-side subsetting) or HRRR's 3 km CONUS grids via byte-range requests against the AWS mirror's index files — the only networked code in the crate.
Zero dependencies by default (grib adds the grib crate, download adds
ureq); no wall clock or unseeded randomness anywhere — cycle date and
forecast hour are explicit parameters.
MIT licensed (see LICENSE).