use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Wind {
pub x_mps: f64,
pub y_mps: f64,
pub z_mps: f64,
}
impl Wind {
pub const ZERO: Self = Self {
x_mps: 0.0,
y_mps: 0.0,
z_mps: 0.0,
};
pub fn from_speed_direction(speed_mps: f64, direction_from_deg: f64) -> Self {
let toward = (direction_from_deg + 180.0).to_radians();
Self {
x_mps: speed_mps * toward.cos(),
y_mps: 0.0,
z_mps: speed_mps * toward.sin(),
}
}
pub fn speed_mps(&self) -> f64 {
self.x_mps.hypot(self.z_mps)
}
pub fn direction_from_deg(&self) -> f64 {
let toward = self.z_mps.atan2(self.x_mps).to_degrees() + 180.0;
toward.rem_euclid(360.0)
}
}
pub trait WindField {
fn wind(&self, time_s: f64, position_m: [f64; 3]) -> Wind;
}
#[derive(Debug, Clone, Copy)]
pub struct ConstantField {
pub wind: Wind,
}
impl WindField for ConstantField {
fn wind(&self, _time_s: f64, _position_m: [f64; 3]) -> Wind {
self.wind
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ProfileLevel {
pub altitude_m: f64,
pub speed_mps: f64,
pub direction_from_deg: f64,
}
#[derive(Debug, Clone)]
pub struct ProfileField {
levels: Vec<ProfileLevel>,
}
impl ProfileField {
pub fn new(mut levels: Vec<ProfileLevel>) -> Result<Self> {
if levels.is_empty() {
return Err(Error::invalid("profile", "needs at least one level"));
}
levels.sort_by(|a, b| a.altitude_m.total_cmp(&b.altitude_m));
for pair in levels.windows(2) {
if pair[0].altitude_m == pair[1].altitude_m {
return Err(Error::invalid(
"profile",
"altitudes must be strictly ascending",
));
}
}
for level in &levels {
if !level.altitude_m.is_finite()
|| !level.speed_mps.is_finite()
|| !level.direction_from_deg.is_finite()
{
return Err(Error::invalid("profile", "levels must be finite"));
}
if level.speed_mps < 0.0 {
return Err(Error::invalid("profile", "speeds must be nonnegative"));
}
}
Ok(Self { levels })
}
pub fn levels(&self) -> &[ProfileLevel] {
&self.levels
}
pub fn wind_at(&self, altitude_m: f64) -> Wind {
let levels = &self.levels;
if altitude_m <= levels[0].altitude_m {
let l = levels[0];
return Wind::from_speed_direction(l.speed_mps, l.direction_from_deg);
}
for pair in levels.windows(2) {
let (lo, hi) = (pair[0], pair[1]);
if altitude_m <= hi.altitude_m {
let f = (altitude_m - lo.altitude_m) / (hi.altitude_m - lo.altitude_m);
let speed = lo.speed_mps + f * (hi.speed_mps - lo.speed_mps);
let mut delta = (hi.direction_from_deg - lo.direction_from_deg) % 360.0;
if delta > 180.0 {
delta -= 360.0;
} else if delta < -180.0 {
delta += 360.0;
}
let direction = lo.direction_from_deg + f * delta;
return Wind::from_speed_direction(speed, direction);
}
}
let l = levels[levels.len() - 1];
Wind::from_speed_direction(l.speed_mps, l.direction_from_deg)
}
}
impl WindField for ProfileField {
fn wind(&self, _time_s: f64, position_m: [f64; 3]) -> Wind {
self.wind_at(position_m[1])
}
}