use crate::field::{Wind, WindField};
use crate::{Error, Result};
#[derive(Debug, Clone, Copy)]
pub struct LogProfile {
pub reference_speed_mps: f64,
pub reference_height_m: f64,
pub roughness_m: f64,
pub direction_from_deg: f64,
}
impl LogProfile {
pub fn new(
reference_speed_mps: f64,
reference_height_m: f64,
roughness_m: f64,
direction_from_deg: f64,
) -> Result<Self> {
let profile = Self {
reference_speed_mps,
reference_height_m,
roughness_m,
direction_from_deg,
};
profile.validate()?;
Ok(profile)
}
fn validate(&self) -> Result<()> {
if !self.reference_speed_mps.is_finite() || self.reference_speed_mps < 0.0 {
return Err(Error::invalid(
"reference_speed_mps",
"must be a nonnegative number",
));
}
if !self.reference_height_m.is_finite() || self.reference_height_m <= 0.0 {
return Err(Error::invalid("reference_height_m", "must be positive"));
}
if !self.roughness_m.is_finite()
|| self.roughness_m <= 0.0
|| self.roughness_m >= self.reference_height_m
{
return Err(Error::invalid(
"roughness_m",
"must be positive and below the reference height",
));
}
Ok(())
}
pub fn speed_at(&self, altitude_m: f64) -> f64 {
if altitude_m <= self.roughness_m {
return 0.0;
}
let z0 = self.roughness_m;
self.reference_speed_mps * (altitude_m / z0).ln() / (self.reference_height_m / z0).ln()
}
}
impl WindField for LogProfile {
fn wind(&self, _time_s: f64, position_m: [f64; 3]) -> Wind {
Wind::from_speed_direction(self.speed_at(position_m[1]), self.direction_from_deg)
}
}
#[derive(Debug, Clone, Copy)]
pub struct EkmanProfile {
pub geostrophic_mps: f64,
pub latitude_deg: f64,
pub eddy_viscosity_m2s: f64,
pub direction_from_deg: f64,
}
impl EkmanProfile {
const OMEGA: f64 = 7.2921159e-5;
pub fn new(
geostrophic_mps: f64,
latitude_deg: f64,
eddy_viscosity_m2s: f64,
direction_from_deg: f64,
) -> Result<Self> {
let profile = Self {
geostrophic_mps,
latitude_deg,
eddy_viscosity_m2s,
direction_from_deg,
};
profile.validate()?;
Ok(profile)
}
fn validate(&self) -> Result<()> {
if !self.geostrophic_mps.is_finite() || self.geostrophic_mps < 0.0 {
return Err(Error::invalid(
"geostrophic_mps",
"must be a nonnegative number",
));
}
if !self.latitude_deg.is_finite() || self.latitude_deg.abs() >= 90.0 {
return Err(Error::invalid(
"latitude_deg",
"must be finite and away from the poles",
));
}
if !self.eddy_viscosity_m2s.is_finite() || self.eddy_viscosity_m2s <= 0.0 {
return Err(Error::invalid("eddy_viscosity_m2s", "must be positive"));
}
Ok(())
}
fn coriolis(&self) -> f64 {
2.0 * Self::OMEGA * self.latitude_deg.to_radians().sin()
}
fn components_at(&self, altitude_m: f64) -> (f64, f64) {
let f = self.coriolis();
if f == 0.0 || altitude_m <= 0.0 {
return (0.0, 0.0);
}
let gamma = (f.abs() / (2.0 * self.eddy_viscosity_m2s)).sqrt();
let decay = (-gamma * altitude_m).exp();
let g = self.geostrophic_mps;
(
g * (1.0 - decay * (gamma * altitude_m).cos()),
f.signum() * g * decay * (gamma * altitude_m).sin(),
)
}
pub fn wind_at(&self, altitude_m: f64) -> Wind {
let (u, v) = self.components_at(altitude_m);
let flow_dir = self.direction_from_deg + 180.0;
let rot = flow_dir.to_radians();
let x = u * rot.cos() + v * rot.sin();
let z = u * rot.sin() - v * rot.cos();
Wind {
x_mps: x,
y_mps: 0.0,
z_mps: z,
}
}
}
impl WindField for EkmanProfile {
fn wind(&self, _time_s: f64, position_m: [f64; 3]) -> Wind {
self.wind_at(position_m[1])
}
}