sounding-analysis 0.20.0

Types and functions for working with weather soundings.
Documentation
use std::collections::HashMap;

use super::*;
use metfor::{HectoPascal, Meters, Quantity};
use sounding_analysis::Layers;
use sounding_analysis::{Result, Sounding};

fn test_layers<F: FnOnce(&Sounding) -> Result<Layers>>(
    snd: &Sounding,
    tgt_int_vals: &HashMap<String, i64>,
    tgt_float_vals: &HashMap<String, Vec<f64>>,
    anal_func: F,
    num_key: &str,
    levels_key: &str,
) {
    if let Some(num_layers) = tgt_int_vals.get(num_key) {
        let num_layers = *num_layers as usize;
        let analysis = anal_func(&snd).unwrap();
        println!("\nanalysis = [");
        for lyr in &analysis {
            println!("{:#?}", lyr);
        }
        println!("]");

        let analyzed_num = analysis.len();
        assert_eq!(num_layers, analyzed_num);

        // Check the pressure levels of these layers.
        if num_layers > 0 {
            if let Some(layer_pressures) = tgt_float_vals.get(levels_key) {
                assert!(layer_pressures.len() >= 2);
                let layer_pressures = layer_pressures.chunks(2);
                let mut count_layers_compared = 0;
                for (lyr, it) in analysis.iter().zip(layer_pressures) {
                    println!(
                        "\nbottom {:#?}  ---  {:#?}",
                        lyr.bottom.pressure.unwrap(),
                        HectoPascal(it[0])
                    );
                    assert!(lyr
                        .bottom
                        .pressure
                        .unwrap()
                        .approx_eq(HectoPascal(it[0]), HectoPascal(1.0)));

                    println!("top {:#?}  ---  {:#?}", lyr.top.pressure.unwrap(), it[1]);
                    assert!(lyr
                        .top
                        .pressure
                        .unwrap()
                        .approx_eq(HectoPascal(it[1]), HectoPascal(1.0)));
                    count_layers_compared += 1;
                }
                assert_eq!(count_layers_compared, num_layers);
            } else {
                panic!("No pressure levels given for analysis target.");
            }
        }
    } else {
        panic!("No num value given..")
    }
}

#[allow(dead_code)] // False alarm - lint is done before macro expansion.
pub fn test_dendritic_layers(
    snd: &Sounding,
    tgt_int_vals: &HashMap<String, i64>,
    tgt_float_vals: &HashMap<String, Vec<f64>>,
) {
    use sounding_analysis::dendritic_snow_zone;

    test_layers(
        snd,
        tgt_int_vals,
        tgt_float_vals,
        dendritic_snow_zone,
        "num dendritic zones",
        "dendritic zone pressures",
    );
}

#[allow(dead_code)] // False alarm - lint is done before macro expansion.
pub fn test_warm_dry_bulb_aloft_and_cold_sfc_layers(
    snd: &Sounding,
    tgt_int_vals: &HashMap<String, i64>,
    tgt_float_vals: &HashMap<String, Vec<f64>>,
) {
    use sounding_analysis::{cold_surface_temperature_layer, warm_temperature_layer_aloft};

    // Test warm layers aloft.
    test_layers(
        snd,
        tgt_int_vals,
        tgt_float_vals,
        warm_temperature_layer_aloft,
        "num warm dry bulb aloft",
        "warm dry bulb layer pressures",
    );

    // Test the cold surface layer.
    if let Some(num_warm_layers) = tgt_int_vals.get("num warm dry bulb aloft") {
        let num_warm_layers = *num_warm_layers as usize;
        let analysis = warm_temperature_layer_aloft(&snd).unwrap();

        println!("num_warm_layers = {}", num_warm_layers);

        // Check the pressure levels of those warm layers.
        if num_warm_layers > 0 {
            // Check out the cold surface layer
            if let Some(cold_surface_layer_pressures) =
                tgt_float_vals.get("cold surface layer pressures")
            {
                let num_cold_surface_layer_pressures = cold_surface_layer_pressures.len();
                assert_eq!(num_cold_surface_layer_pressures, 2);

                let cold_sfc_analysis = cold_surface_temperature_layer(&snd, &analysis)
                    .unwrap() // Result
                    .unwrap(); // Option - ok to unwrap this because we checked num_warm_layers

                println!("cold_sfc_analysis: {:#?}", cold_sfc_analysis);

                let cold_surface_layer_pressures = cold_surface_layer_pressures.chunks(2);
                for (lyr, it) in [cold_sfc_analysis].iter().zip(cold_surface_layer_pressures) {
                    println!(
                        "\nbottom {:#?}  ---  {:#?}",
                        lyr.bottom.pressure.unwrap(),
                        it[0]
                    );
                    assert!(lyr
                        .bottom
                        .pressure
                        .unwrap()
                        .approx_eq(HectoPascal(it[0]), HectoPascal(1.0)));

                    println!("top {:#?}  ---  {:#?}", lyr.top.pressure.unwrap(), it[1]);
                    assert!(lyr
                        .top
                        .pressure
                        .unwrap()
                        .approx_eq(HectoPascal(it[1]), HectoPascal(1.0)));
                }
            } else {
                panic!("No pressure levels given for cold surface layer.");
            }
        }
    }
}

#[allow(dead_code)] // False alarm - lint is done before macro expansion.
pub fn test_warm_wet_bulb_aloft(
    snd: &Sounding,
    tgt_int_vals: &HashMap<String, i64>,
    tgt_float_vals: &HashMap<String, Vec<f64>>,
) {
    use sounding_analysis::warm_wet_bulb_layer_aloft;

    test_layers(
        snd,
        tgt_int_vals,
        tgt_float_vals,
        warm_wet_bulb_layer_aloft,
        "num warm wet bulb aloft",
        "warm wet bulb layer pressures",
    );
}

#[allow(dead_code)] // False alarm - lint is done before macro expansion.
pub fn test_layer_agl(snd: &Sounding, tgt_float_vals: &HashMap<String, Vec<f64>>) {
    use sounding_analysis::layer_agl;

    let analysis = layer_agl(snd, Meters(6000.0)).expect("error creating layer");
    println!("\n6km AGL layer: {:#?}", analysis);
    if let Some(agl_layer_pressures) = tgt_float_vals.get("6km agl layer pressures") {
        assert_eq!(agl_layer_pressures.len(), 2);
        let agl_layer_pressures = agl_layer_pressures.chunks(2);
        for (lyr, it) in [analysis].iter().zip(agl_layer_pressures) {
            println!(
                "bottom {:#?}  ---  {:#?}",
                lyr.bottom.pressure.unwrap(),
                it[0]
            );
            assert!(lyr
                .bottom
                .pressure
                .unwrap()
                .approx_eq(HectoPascal(it[0]), HectoPascal(1.0)));

            println!("top {:#?}  ---  {:#?}", lyr.top.pressure.unwrap(), it[1]);
            assert!(lyr
                .top
                .pressure
                .unwrap()
                .approx_eq(HectoPascal(it[1]), HectoPascal(1.0)));
        }
    } else {
        panic!("No pressure levels given for agl layers.");
    }
}

#[allow(dead_code)] // False alarm - lint is done before macro expansion.
pub fn test_pressure_layer(snd: &Sounding, tgt_float_vals: &HashMap<String, Vec<f64>>) {
    use sounding_analysis::pressure_layer;

    let analysis = pressure_layer(snd, HectoPascal(700.0), HectoPascal(500.0)).unwrap();
    println!("\n700-500 hPa layer: {:#?}", analysis);
    if let Some(pressure_layer_heights) = tgt_float_vals.get("700-500 hPa layer heights") {
        assert_eq!(pressure_layer_heights.len(), 2);
        let pressure_layer_heights = pressure_layer_heights.chunks(2);
        for (lyr, it) in [analysis].iter().zip(pressure_layer_heights) {
            println!(
                "bottom {:#?}  ---  {:#?}",
                lyr.bottom.height.unwrap(),
                it[0]
            );
            assert!(lyr
                .bottom
                .height
                .unwrap()
                .approx_eq(Meters(it[0]), Meters(1.0)));

            println!("top {:#?}  ---  {:#?}", lyr.top.height.unwrap(), it[1]);
            assert!(lyr
                .top
                .height
                .unwrap()
                .approx_eq(Meters(it[1]), Meters(1.0)));
        }
    } else {
        panic!("No heights given for pressure layer.");
    }
}

#[allow(dead_code)] // False alarm - lint is done before macro expansion.
pub fn test_inversion_layers(
    snd: &Sounding,
    tgt_int_vals: &HashMap<String, i64>,
    tgt_float_vals: &HashMap<String, Vec<f64>>,
) {
    use sounding_analysis::inversions;

    let test_inversions = |snd: &Sounding| inversions(snd, HectoPascal(300.0));

    test_layers(
        snd,
        tgt_int_vals,
        tgt_float_vals,
        test_inversions,
        "num inversions",
        "inversion layer pressures",
    );
}