1use std::fmt;
4
5use hpr_core::interp::Side;
6use serde::{Deserialize, Serialize};
7
8use crate::error::AtmosError;
9use crate::profile::SoundingProfile;
10use crate::ussa76::Ussa76;
11
12#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
14pub struct AirState {
15 pub temperature_k: f64,
17 pub pressure_pa: f64,
19 pub density_kg_m3: f64,
21 pub speed_of_sound_m_s: f64,
23 pub dynamic_viscosity_pa_s: f64,
25}
26
27impl AirState {
28 pub fn kinematic_viscosity_m2_s(&self) -> f64 {
30 self.dynamic_viscosity_pa_s / self.density_kg_m3
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
37pub struct AirSample {
38 pub air: AirState,
40 pub extrapolated: Option<Side>,
43}
44
45pub trait Atmosphere: fmt::Debug + Send + Sync {
51 fn air(&self, height_msl_m: f64) -> Result<AirSample, AtmosError>;
58}
59
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
62#[serde(tag = "model", rename_all = "snake_case")]
63#[non_exhaustive]
64pub enum AtmosphereModel {
65 Standard(Ussa76),
67 Sounding(Box<SoundingProfile>),
69}
70
71impl Default for AtmosphereModel {
72 fn default() -> Self {
73 AtmosphereModel::Standard(Ussa76::standard())
74 }
75}
76
77impl Atmosphere for AtmosphereModel {
78 fn air(&self, height_msl_m: f64) -> Result<AirSample, AtmosError> {
79 match self {
80 AtmosphereModel::Standard(model) => model.sample(height_msl_m),
81 AtmosphereModel::Sounding(model) => model.sample(height_msl_m),
82 }
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn atmosphere_models_round_trip_through_json() {
92 let standard = AtmosphereModel::Standard(Ussa76::with_offset(10.0, 99_000.0).unwrap());
93 let json = serde_json::to_string(&standard).unwrap();
94 assert_eq!(
95 json,
96 r#"{"model":"standard","temperature_offset_k":10.0,"sea_level_pressure_pa":99000.0}"#
97 );
98 assert_eq!(
99 serde_json::from_str::<AtmosphereModel>(&json).unwrap(),
100 standard
101 );
102 let sounding = r#"{"model":"sounding","latitude_rad":0.6,"levels":[
103 {"height_msl_m":0.0,"temperature_k":290.0,"pressure_pa":100000.0}]}"#;
104 let model: AtmosphereModel = serde_json::from_str(sounding).unwrap();
105 assert!(matches!(model, AtmosphereModel::Sounding(_)));
106 let air = model.air(0.0).unwrap().air;
107 assert_eq!(air.pressure_pa, 100_000.0);
108 assert_eq!(
109 AtmosphereModel::default().air(0.0).unwrap(),
110 Ussa76::standard().sample(0.0).unwrap()
111 );
112 }
113}