use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RunwayEnd {
pub id: String,
pub true_alignment_deg: Option<f64>,
pub elevation_ft: Option<f64>,
}
impl RunwayEnd {
pub fn new(id: impl Into<String>) -> Self {
Self {
id: id.into(),
true_alignment_deg: None,
elevation_ft: None,
}
}
#[must_use]
pub fn with_true_alignment_deg(mut self, deg: Option<f64>) -> Self {
self.true_alignment_deg = deg;
self
}
#[must_use]
pub fn with_elevation_ft(mut self, ft: Option<f64>) -> Self {
self.elevation_ft = ft;
self
}
#[must_use]
pub fn wind_components_kt(&self, wind_dir_true: f64, wind_speed_kt: f64) -> Option<(f64, f64)> {
let alignment = self.true_alignment_deg?;
if !wind_dir_true.is_finite() || !wind_speed_kt.is_finite() {
return None;
}
let angle = (wind_dir_true - alignment).to_radians();
let headwind = wind_speed_kt * angle.cos();
let crosswind = (wind_speed_kt * angle.sin()).abs();
Some((headwind, crosswind))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Runway {
pub airport_ident: String,
pub designator: String,
pub length_ft: Option<f64>,
pub width_ft: Option<f64>,
pub surface: Option<String>,
pub country_code: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ends: Vec<RunwayEnd>,
}
impl Runway {
pub fn new(airport_ident: impl Into<String>, designator: impl Into<String>) -> Self {
Self {
airport_ident: airport_ident.into(),
designator: designator.into(),
length_ft: None,
width_ft: None,
surface: None,
country_code: None,
ends: Vec::new(),
}
}
#[must_use]
pub fn with_length_ft(mut self, ft: Option<f64>) -> Self {
self.length_ft = ft;
self
}
#[must_use]
pub fn with_width_ft(mut self, ft: Option<f64>) -> Self {
self.width_ft = ft;
self
}
#[must_use]
pub fn with_surface(mut self, surface: Option<String>) -> Self {
self.surface = surface;
self
}
#[must_use]
pub fn with_country_code(mut self, country_code: Option<String>) -> Self {
self.country_code = country_code;
self
}
#[must_use]
pub fn with_ends(mut self, ends: Vec<RunwayEnd>) -> Self {
self.ends = ends;
self
}
#[must_use]
pub fn crosswind_kt(&self, wind_dir_true: f64, wind_speed_kt: f64) -> Option<f64> {
self.ends
.iter()
.find_map(|end| end.wind_components_kt(wind_dir_true, wind_speed_kt))
.map(|(_, crosswind)| crosswind)
}
}
#[cfg(test)]
mod tests;