use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CloudField {
pub layers: Vec<CloudLayer>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct CloudLayer {
pub base_altitude_m: f32,
pub top_altitude_m: f32,
pub coverage: f32,
pub cloud_type: u8,
pub optical_depth: f32,
}
impl CloudField {
#[must_use]
pub fn single_layer(base_m: f32, thickness_m: f32, coverage: f32, cloud_type: u8) -> Self {
Self {
layers: vec![CloudLayer {
base_altitude_m: base_m,
top_altitude_m: base_m + thickness_m,
coverage: coverage.clamp(0.0, 1.0),
cloud_type,
optical_depth: coverage * 5.0,
}],
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WindVectorField {
pub vectors: Vec<[f32; 3]>,
pub dimensions: [usize; 3],
pub origin: [f32; 3],
pub spacing_h: f32,
pub spacing_v: f32,
pub max_speed: f32,
}
impl WindVectorField {
#[must_use]
pub fn uniform_surface(
nx: usize,
ny: usize,
origin: [f32; 3],
spacing: f32,
wind_u: f32,
wind_v: f32,
) -> Self {
let count = nx * ny;
let vectors = vec![[wind_u, wind_v, 0.0]; count];
let speed = (wind_u * wind_u + wind_v * wind_v).sqrt();
Self {
vectors,
dimensions: [nx, ny, 1],
origin,
spacing_h: spacing,
spacing_v: 0.0,
max_speed: speed,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PrecipitationMap {
pub rates: Vec<f32>,
pub dimensions: [usize; 2],
pub origin: [f32; 2],
pub spacing: f32,
pub precip_type: u8,
pub max_rate: f32,
}
impl PrecipitationMap {
#[must_use]
pub fn uniform(
nx: usize,
ny: usize,
origin: [f32; 2],
spacing: f32,
rate_mm_hr: f32,
precip_type: u8,
) -> Self {
let count = nx * ny;
Self {
rates: vec![rate_mm_hr; count],
dimensions: [nx, ny],
origin,
spacing,
precip_type,
max_rate: rate_mm_hr,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AtmosphericSection {
pub temperature: Vec<f32>,
pub pressure: Vec<f32>,
pub dimensions: [usize; 2],
pub horizontal_range: [f32; 2],
pub altitude_range: [f32; 2],
}
impl AtmosphericSection {
#[must_use]
pub fn isa_section(x_range: [f32; 2], nx: usize, max_altitude_m: f32, n_alt: usize) -> Self {
let count = nx * n_alt;
let mut temperature = Vec::with_capacity(count);
let mut pressure = Vec::with_capacity(count);
let alt_step = if n_alt > 1 {
max_altitude_m / (n_alt - 1) as f32
} else {
0.0
};
for iy in 0..n_alt {
let alt = iy as f32 * alt_step;
let t = crate::atmosphere::standard_temperature(alt as f64) as f32;
let p = crate::atmosphere::standard_pressure(alt as f64) as f32;
for _ in 0..nx {
temperature.push(t);
pressure.push(p);
}
}
Self {
temperature,
pressure,
dimensions: [nx, n_alt],
horizontal_range: x_range,
altitude_range: [0.0, max_altitude_m],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cloud_field_single_layer() {
let field = CloudField::single_layer(2000.0, 500.0, 0.7, 3);
assert_eq!(field.layers.len(), 1);
assert!((field.layers[0].base_altitude_m - 2000.0).abs() < 0.1);
assert!((field.layers[0].top_altitude_m - 2500.0).abs() < 0.1);
assert!((field.layers[0].coverage - 0.7).abs() < 0.01);
}
#[test]
fn cloud_field_coverage_clamps() {
let field = CloudField::single_layer(1000.0, 200.0, 1.5, 0);
assert!((field.layers[0].coverage - 1.0).abs() < 0.01);
}
#[test]
fn wind_field_uniform() {
let field = WindVectorField::uniform_surface(4, 4, [0.0; 3], 1000.0, 5.0, 3.0);
assert_eq!(field.vectors.len(), 16);
assert_eq!(field.dimensions, [4, 4, 1]);
for v in &field.vectors {
assert_eq!(v[0], 5.0);
assert_eq!(v[1], 3.0);
assert_eq!(v[2], 0.0);
}
assert!((field.max_speed - (5.0_f32.powi(2) + 3.0_f32.powi(2)).sqrt()).abs() < 0.01);
}
#[test]
fn precipitation_map_uniform() {
let map = PrecipitationMap::uniform(3, 3, [0.0, 0.0], 500.0, 10.0, 0);
assert_eq!(map.rates.len(), 9);
assert_eq!(map.precip_type, 0);
assert!((map.max_rate - 10.0).abs() < 0.01);
}
#[test]
fn atmospheric_section_isa() {
let section = AtmosphericSection::isa_section([0.0, 10000.0], 5, 11000.0, 12);
assert_eq!(section.temperature.len(), 60); assert_eq!(section.pressure.len(), 60);
assert!((section.temperature[0] - 288.15).abs() < 0.1);
assert!((section.pressure[0] - 101325.0).abs() < 1.0);
let top_idx = 11 * 5; assert!(section.temperature[top_idx] < 230.0);
}
#[test]
fn atmospheric_section_single_level() {
let section = AtmosphericSection::isa_section([0.0, 1000.0], 3, 0.0, 1);
assert_eq!(section.temperature.len(), 3);
}
#[test]
fn cloud_field_serializes() {
let field = CloudField::single_layer(3000.0, 1000.0, 0.5, 2);
let json = serde_json::to_string(&field);
assert!(json.is_ok());
}
#[test]
fn wind_field_serializes() {
let field = WindVectorField::uniform_surface(2, 2, [0.0; 3], 100.0, 1.0, 0.0);
let json = serde_json::to_string(&field);
assert!(json.is_ok());
}
}