# 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](docs/winds-engine-plan.md).
```rust
use winds_engine::{Sounding, WindField, validate};
// 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 = Sounding::parse("287 0.0 0\n305 3.09 125\n610 3.60 145\n")?;
// 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(0.0, [0.0, 500.0, 0.0]); // 500 m above sea level
// Or score a candidate profile against the measurement.
let report = validate::validate(&profile, &sounding);
```
- `WindField` trait: stateless, deterministic `wind(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 in `to_profile_field()`, so the profile reads
directly in a frame whose y is altitude above sea level, as in
rockets-engine (with `BallisticForces` and in its flat-frame CLI, whose
ground sits at `--ground-altitude-m`, sea level by default).
- Datum conversion: `LogProfile` and `EkmanProfile` take y as height above
the ground, where they are calm. This crate's staged types have no offset
wrapper; ballistics-engine's `HeightOffset` (from its next release, which
rockets-engine's `=0.48.0` pin does not have) wraps only
ballistics-engine's own `WindField`, not this crate's. So a caller in a
sea-level frame either reads them at `y - ground elevation` at its single
call site, or builds
ballistics-engine's `ProfileField`/`LogProfile`/`EkmanProfile` from the
same values and wraps those in `HeightOffset` (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::wind` takes and returns `nalgebra::Vector3<f64>` and
there is no `Wind` type, `ConstantField` is a tuple struct over that
vector, `LogProfile`/`EkmanProfile` keep their fields private behind
getters, and constructors return `WindFieldError`. 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 a `ProfileField` at 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.
- `terrain`: a diagnostic mass-consistent terrain wind — a `HeightGrid`
(ASCII DEM + raw arrays) plus `TerrainFlowField`, which adjusts a base
field to satisfy 2-D horizontal mass conservation over terrain,
constrained by surface wind `Monitor`s (one per reading; `from_geo` and
`monitors_from_geo_csv` project GPS lat/lon and compass directions into
the field's frame).
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).