1#[derive(Debug, Clone)]
8struct AtmosphereLayer {
9 base_altitude: f64,
11 base_temperature: f64,
13 base_pressure: f64,
15 lapse_rate: f64,
17}
18
19const G_ACCEL_MPS2: f64 = 9.80665;
21const R_AIR: f64 = 287.0531; const GAMMA: f64 = 1.4; const R_DRY: f64 = 287.05; const R_VAPOR: f64 = 461.495; const R: f64 = 8.314472; const M_A: f64 = 28.96546e-3; const M_V: f64 = 18.01528e-3; const ICAO_LAYERS: &[AtmosphereLayer] = &[
34 AtmosphereLayer {
36 base_altitude: 0.0,
37 base_temperature: 288.15, base_pressure: 101325.0, lapse_rate: -0.0065, },
41 AtmosphereLayer {
43 base_altitude: 11000.0,
44 base_temperature: 216.65, base_pressure: 22632.1, lapse_rate: 0.0, },
48 AtmosphereLayer {
50 base_altitude: 20000.0,
51 base_temperature: 216.65, base_pressure: 5474.89, lapse_rate: 0.001, },
55 AtmosphereLayer {
57 base_altitude: 32000.0,
58 base_temperature: 228.65, base_pressure: 868.02, lapse_rate: 0.0028, },
62 AtmosphereLayer {
64 base_altitude: 47000.0,
65 base_temperature: 270.65, base_pressure: 110.91, lapse_rate: 0.0, },
69 AtmosphereLayer {
71 base_altitude: 51000.0,
72 base_temperature: 270.65, base_pressure: 66.94, lapse_rate: -0.0028, },
76 AtmosphereLayer {
78 base_altitude: 71000.0,
79 base_temperature: 214.65, base_pressure: 3.96, lapse_rate: -0.002, },
83];
84
85fn calculate_icao_standard_atmosphere(altitude_m: f64) -> (f64, f64) {
96 let altitude = altitude_m.clamp(0.0, 84000.0);
98
99 let layer = ICAO_LAYERS
101 .iter()
102 .rev()
103 .find(|layer| altitude >= layer.base_altitude)
104 .unwrap_or(&ICAO_LAYERS[0]);
105
106 let height_diff = altitude - layer.base_altitude;
107 let temperature = layer.base_temperature + layer.lapse_rate * height_diff;
108
109 let pressure = if layer.lapse_rate.abs() < 1e-10 {
110 layer.base_pressure * (-G_ACCEL_MPS2 * height_diff / (R_AIR * layer.base_temperature)).exp()
112 } else {
113 let temp_ratio = temperature / layer.base_temperature;
115 layer.base_pressure * temp_ratio.powf(-G_ACCEL_MPS2 / (layer.lapse_rate * R_AIR))
116 };
117
118 (temperature, pressure)
119}
120
121pub fn calculate_atmosphere(
132 altitude_m: f64,
133 temp_override_c: Option<f64>,
134 press_override_hpa: Option<f64>,
135 humidity_percent: f64,
136) -> (f64, f64) {
137 let (temp_k, pressure_pa) = if temp_override_c.is_some() && press_override_hpa.is_some() {
139 (
141 temp_override_c.unwrap() + 273.15,
142 press_override_hpa.unwrap() * 100.0,
143 )
144 } else {
145 let (std_temp_k, std_pressure_pa) = calculate_icao_standard_atmosphere(altitude_m);
147
148 let final_temp_k = if let Some(temp_c) = temp_override_c {
149 temp_c + 273.15
150 } else {
151 std_temp_k
152 };
153
154 let final_pressure_pa = if let Some(press_hpa) = press_override_hpa {
155 press_hpa * 100.0
156 } else {
157 std_pressure_pa
158 };
159
160 (final_temp_k, final_pressure_pa)
161 };
162
163 let humidity_clamped = humidity_percent.clamp(0.0, 100.0);
165
166 let temp_c = temp_k - 273.15;
168 let es_hpa = if temp_c >= 0.0 {
169 6.1121 * (18.678 - temp_c / 234.5) * (temp_c / (257.14 + temp_c)).exp()
171 } else {
172 6.1115 * (23.036 - temp_c / 333.7) * (temp_c / (279.82 + temp_c)).exp()
174 };
175
176 let vapor_pressure_pa = humidity_clamped / 100.0 * es_hpa * 100.0;
178 let dry_pressure_pa = (pressure_pa - vapor_pressure_pa).max(0.0);
179
180 let density = dry_pressure_pa / (R_DRY * temp_k) + vapor_pressure_pa / (R_VAPOR * temp_k);
182
183 let mole_fraction_vapor = vapor_pressure_pa / pressure_pa;
186 let temp_c_abs = temp_k;
187
188 let gamma_moist = GAMMA * (1.0 - mole_fraction_vapor * 0.11);
190
191 let r_moist = R_AIR * (1.0 + 0.6078 * mole_fraction_vapor);
193
194 let speed_of_sound_base = (gamma_moist * r_moist * temp_c_abs).sqrt();
196
197 let humidity_correction = 1.0 + 0.0001 * humidity_clamped * (temp_c / 20.0);
199 let speed_of_sound = speed_of_sound_base * humidity_correction;
200
201 (density, speed_of_sound)
202}
203
204pub fn calculate_air_density_cimp(temp_c: f64, pressure_hpa: f64, humidity_percent: f64) -> f64 {
214 let t_k = temp_c + 273.15;
215
216 let p_sv = enhanced_saturation_vapor_pressure(t_k);
218
219 let f = enhanced_enhancement_factor(pressure_hpa, temp_c);
221
222 let p_v = humidity_percent.clamp(0.0, 100.0) / 100.0 * f * p_sv;
224
225 let x_v = p_v / pressure_hpa;
227
228 let z = enhanced_compressibility_factor(pressure_hpa, t_k, x_v);
230
231 let density = ((pressure_hpa * M_A) / (z * R * t_k)) * (1.0 - x_v * (1.0 - M_V / M_A));
234
235 density * 100.0
238}
239
240#[inline(always)]
243fn enhanced_saturation_vapor_pressure(t_k: f64) -> f64 {
244 const A: [f64; 6] = [
246 -7.85951783,
247 1.84408259,
248 -11.7866497,
249 22.6807411,
250 -15.9618719,
251 1.80122502,
252 ];
253
254 let t_k_safe = t_k.max(173.15); let tau = 1.0 - t_k_safe / 647.096; let ln_p_ratio = (647.096 / t_k_safe)
259 * (A[0] * tau
260 + A[1] * tau.powf(1.5)
261 + A[2] * tau.powf(3.0)
262 + A[3] * tau.powf(3.5)
263 + A[4] * tau.powf(4.0)
264 + A[5] * tau.powf(7.5));
265
266 220640.0 * ln_p_ratio.exp() }
268
269#[inline(always)]
271fn enhanced_enhancement_factor(p: f64, t: f64) -> f64 {
272 const ALPHA: f64 = 1.00062;
273 const BETA: f64 = 3.14e-8;
274 const GAMMA: f64 = 5.6e-7;
275 const DELTA: f64 = 1.2e-10; ALPHA + BETA * p + GAMMA * t * t + DELTA * p * t
278}
279
280#[inline(always)]
282fn enhanced_compressibility_factor(p: f64, t_k: f64, x_v: f64) -> f64 {
283 const A0: f64 = 1.58123e-6;
285 const A1: f64 = -2.9331e-8;
286 const A2: f64 = 1.1043e-10;
287 const B0: f64 = 5.707e-6;
288 const B1: f64 = -2.051e-8;
289 const C0: f64 = 1.9898e-4;
290 const C1: f64 = -2.376e-6;
291 const D: f64 = 1.83e-11;
292 const E: f64 = -0.765e-8;
293
294 const F0: f64 = 2.1e-12;
296 const F1: f64 = -1.1e-14;
297
298 let t_k_safe = t_k.max(173.15); let t = t_k_safe - 273.15;
301 let p_t = p / t_k_safe;
302
303 let z_second_order =
304 1.0 - p_t * (A0 + A1 * t + A2 * t * t + (B0 + B1 * t) * x_v + (C0 + C1 * t) * x_v * x_v);
305
306 let z_third_order = p_t * p_t * (D + E * x_v * x_v);
307
308 let z_fourth_order = p_t * p_t * p_t * (F0 + F1 * x_v * x_v * x_v);
310
311 z_second_order + z_third_order + z_fourth_order
312}
313
314pub fn get_local_atmosphere(
326 altitude_m: f64,
327 base_alt: f64,
328 base_temp_c: f64,
329 base_press_hpa: f64,
330 base_ratio: f64,
331) -> (f64, f64) {
332 let altitude_m_rounded = altitude_m.round();
334 let height_diff = altitude_m_rounded - base_alt;
335
336 let lapse_rate = determine_local_lapse_rate(altitude_m_rounded);
338
339 let temp_c = base_temp_c + lapse_rate * height_diff;
341 let temp_k = temp_c + 273.15;
342 let base_temp_k = base_temp_c + 273.15;
343
344 let pressure_hpa = if lapse_rate.abs() < 1e-10 {
346 base_press_hpa * (-G_ACCEL_MPS2 * height_diff / (R_AIR * base_temp_k)).exp()
348 } else {
349 let temp_ratio = temp_k / base_temp_k;
351 base_press_hpa * temp_ratio.powf(-G_ACCEL_MPS2 / (lapse_rate * R_AIR))
352 };
353
354 let density_ratio = base_ratio * (base_temp_k * pressure_hpa) / (base_press_hpa * temp_k);
356 let density = density_ratio * 1.225;
357
358 let speed_of_sound = (temp_k * 401.874).sqrt(); (density, speed_of_sound)
362}
363
364#[inline(always)]
366fn determine_local_lapse_rate(altitude_m: f64) -> f64 {
367 let layer = ICAO_LAYERS
369 .iter()
370 .rev()
371 .find(|layer| altitude_m >= layer.base_altitude)
372 .unwrap_or(&ICAO_LAYERS[0]);
373
374 layer.lapse_rate
375}
376
377#[inline(always)]
386pub fn get_direct_atmosphere(density: f64, speed_of_sound: f64) -> (f64, f64) {
387 (density, speed_of_sound)
388}
389
390pub fn calculate_air_density_cipm(temp_c: f64, pressure_hpa: f64, humidity_percent: f64) -> f64 {
392 calculate_air_density_cimp(temp_c, pressure_hpa, humidity_percent)
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 #[test]
400 fn test_icao_standard_atmosphere() {
401 let (temp, press) = calculate_icao_standard_atmosphere(0.0);
403 assert!((temp - 288.15).abs() < 0.01);
404 assert!((press - 101325.0).abs() < 1.0);
405
406 let (temp_11km, press_11km) = calculate_icao_standard_atmosphere(11000.0);
408 assert!((temp_11km - 216.65).abs() < 0.01);
409 assert!(press_11km < 101325.0);
410
411 let (temp_25km, _) = calculate_icao_standard_atmosphere(25000.0);
413 assert!(temp_25km > 216.65); }
415
416 #[test]
417 fn test_enhanced_atmosphere_sea_level() {
418 let (density, speed) = calculate_atmosphere(0.0, None, None, 0.0);
419 assert!((density - 1.225).abs() < 0.01);
420 assert!((speed - 340.0).abs() < 1.0);
421 }
422
423 #[test]
424 fn test_enhanced_atmosphere_with_humidity() {
425 let (density_dry, speed_dry) = calculate_atmosphere(0.0, None, None, 0.0);
426 let (density_humid, speed_humid) = calculate_atmosphere(0.0, None, None, 80.0);
427
428 assert!(density_humid < density_dry);
430 assert!(speed_humid > speed_dry);
432 }
433
434 #[test]
435 fn test_enhanced_atmosphere_stratosphere() {
436 let (density_20km, speed_20km) = calculate_atmosphere(20000.0, None, None, 0.0);
438 let (density_30km, speed_30km) = calculate_atmosphere(30000.0, None, None, 0.0);
439
440 assert!(density_30km < density_20km);
442 assert!(speed_30km > speed_20km);
444 }
445
446 #[test]
447 fn test_enhanced_cimp_density() {
448 let density = calculate_air_density_cimp(15.0, 1013.25, 0.0);
449 assert!((density - 1.225).abs() < 0.01);
450
451 let density_humid = calculate_air_density_cimp(15.0, 1013.25, 50.0);
453 assert!(density_humid < density);
454 }
455
456 #[test]
457 fn test_variable_lapse_rates() {
458 let lapse_tropo = determine_local_lapse_rate(5000.0);
460 let lapse_strato = determine_local_lapse_rate(25000.0);
461
462 assert!((lapse_tropo - (-0.0065)).abs() < 0.0001);
463 assert!(lapse_strato > 0.0); }
465}