griblib_rust 0.1.1

A collection of utilities to convert GRIB2 data file
Documentation
//! # GribLib->GribTool
//!
//! `gribtool` is a collection of functions to write GRIB2 data file
//!

use lazy_static::lazy_static;
use libc::{c_char, c_double, c_int, c_uint, c_ulonglong};
use std::{
    env,
    ffi::CString,
    ptr,
    sync::{Arc, Mutex},
};

pub mod mi4tool;
pub const GRIB_ELEMENTS: [&str; 10] = [
    "ECT", "EDA10", "ER03", "ER06", "ER12", "ER24", "ERHA", "TMP", "TMAX", "TMIN",
];

const LIB_NAME: &str = "gribtool_lib";
const LIB_MODE: u8 = 2;
static mut DEBUG: bool = false;

type FuncInitContext = unsafe extern "C" fn() -> c_ulonglong;
type FuncMi4textGrib = unsafe extern "C" fn(
    *const c_char,
    *const c_char,
    *const c_char,
    c_uint,
    *const c_char,
) -> c_int;
type FuncMi4dataGrib = unsafe extern "C" fn(
    *const c_char,
    *const c_char,
    *const c_char,
    c_uint,
    *const c_char,
) -> c_int;
type FuncGribIndex = extern "C" fn(c_ulonglong, *const c_char, *const c_char) -> c_ulonglong;
type FuncFreeGribIndex = extern "C" fn(c_ulonglong) -> c_int;
type FuncAddGribSample = unsafe extern "C" fn(
    c_ulonglong,
    *const c_char,
    *const c_double,
    c_uint,
    c_uint,
    *const c_double,
    *const c_double,
    c_uint,
    c_uint,
    c_uint,
    *const c_char,
) -> c_int;
type FuncAddGribTempl = unsafe extern "C" fn(
    c_ulonglong,
    *const c_char,
    *const c_double,
    c_uint,
    c_uint,
    *const c_double,
    *const c_double,
    c_uint,
    c_uint,
    c_uint,
    c_uint,
    *const c_char,
) -> c_int;

#[derive(Debug)]
struct GribLib {
    _lib: libloading::Library,
    //init_context: FuncInitContext,
    mi4data_grib: FuncMi4dataGrib,
    mi4text_grib: FuncMi4textGrib,
    grib_file_index: FuncGribIndex,
    free_grib_index: FuncFreeGribIndex,
    add_grib_sample: FuncAddGribSample,
    add_grib_templ: FuncAddGribTempl,
}

impl GribLib {
    fn load_func<F>(lib: &libloading::Library, name: &str) -> F
    where
        F: Copy,
    {
        unsafe {
            let name = name.as_bytes();
            let func: libloading::Symbol<F> = lib.get(name).unwrap();
            let func = *func.into_raw();
            return func;
        }
    }

    fn new(path: &str) -> GribLib {
        let mut libname = LIB_NAME.to_string();
        let mut libext = "dll";
        if cfg!(target_os = "linux") {
            libext = "so";
        }
        if env::var("DEBUG").is_ok() {
            unsafe {
                DEBUG = true;
            }
            libname = format!("{}-dbg", libname);
        }
        let mut libdir = path;
        if libdir.is_empty() {
            libdir = "./";
        }
        let libpath = format!("{}{}.{}", libdir, libname, libext);
        unsafe {
            let lib = match libloading::Library::new(&libpath) {
                Ok(lib) => lib,
                Err(e) => panic!("Load griblib error - {}, {}", libpath, e),
            };
            //static lifecycle (so struct GribLib no need _lib field anymore)
            //let lib: &'static libloading::Library = Box::leak(Box::new(lib));
            let init_context: FuncInitContext = GribLib::load_func(&lib, "init_context");
            let mi4data_grib: FuncMi4dataGrib = GribLib::load_func(&lib, "mi4data_grib");
            let mi4text_grib: FuncMi4textGrib = GribLib::load_func(&lib, "mi4text_grib");
            let grib_file_index: FuncGribIndex = GribLib::load_func(&lib, "grib_file_index");
            let free_grib_index: FuncFreeGribIndex = GribLib::load_func(&lib, "free_grib_index");
            let add_grib_sample: FuncAddGribSample = GribLib::load_func(&lib, "add_grib_sample");
            let add_grib_templ: FuncAddGribTempl = GribLib::load_func(&lib, "add_grib_templ");
            let ctx = init_context();
            if ctx < 1 {
                panic!("Init grib context error");
            }

            let glib = GribLib {
                _lib: lib,
                //init_context,
                mi4data_grib,
                mi4text_grib,
                grib_file_index,
                free_grib_index,
                add_grib_sample,
                add_grib_templ,
            };
            if DEBUG {
                println!("load grib lib: {:#?}", glib);
            }
            glib
        }
    }

    pub fn gribfile_index(&self, context: c_ulonglong, gribf: &str, etype: &str) -> c_ulonglong {
        let _lc = GLOCK.lock().unwrap();
        let gindex = (self.grib_file_index)(context, cstr_chars(gribf), cstr_chars(etype));
        drop(_lc);
        gindex
    }
}

lazy_static! {
    static ref GLIB: GribLib = GribLib::new("");
    static ref GLOCK: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
}

fn cstr_chars(s: &str) -> *mut c_char {
    let s = CString::new(s.as_bytes().to_vec()).unwrap();
    return s.into_raw();
}

/// MICAPS4 text directory write to GRIB2 file
/// # Examples
/// ```
/// use griblib_rust::gribtool;
///
/// let dir = "/data/mi4text/TMP/";
/// let etype = "TMP";
/// let date_hour = 2023021008;
/// let outf = "/data/grib2/tmp.GRB2";
/// let num = gribtool::mi4text_grib(dir, "", etype, date_hour, outf);
/// ```
pub fn mi4text_grib(mi4dir: &str, gribf: &str, etype: &str, date_hour: u32, outf: &str) -> i32 {
    if ["WIU", "WIV"].contains(&etype) {
        return 0;
    }
    let mut count = 0;
    let c_gribf = cstr_chars(gribf);
    let c_etype = cstr_chars(etype);
    if LIB_MODE == 2 {
        let cindex = GLIB.gribfile_index(0, gribf, etype);
        if cindex < 1 {
            eprint!("Create grib file index error - {}, {}", etype, cindex);
        }
        let has_templ = cindex > 0;

        let infos = match mi4tool::read_mi4dir(mi4dir, date_hour) {
            Ok(o) => o,
            Err(e) => {
                eprint!("Read micaps4 directory error - {}, {}", mi4dir, e);
                return 0;
            }
        };
        let fnum = infos.len();

        let mut step_idx = 0;
        for n in 0..fnum {
            let info = &infos[n];
            let _longitudes = [info.lon_from, info.lon_to, info.lon_step].as_ptr();
            let _latitudes = [info.lat_from, info.lat_to, info.lat_step].as_ptr();
            let last_index = if n >= fnum - 1 { 0 } else { step_idx + 1 };
            let longitudes = ptr::null::<f64>();
            let latitudes = ptr::null::<f64>();
            unsafe {
                if has_templ {
                    let _lock = GLOCK.lock().unwrap();
                    let num = (GLIB.add_grib_templ)(
                        cindex,
                        c_etype,
                        info.values.as_ptr(),
                        info.date_hour,
                        info.mtime,
                        longitudes,
                        latitudes,
                        info.width,
                        info.height,
                        step_idx,
                        last_index,
                        cstr_chars(outf),
                    );
                    drop(_lock);
                    count += num;
                } else {
                    let num = (GLIB.add_grib_sample)(
                        0,
                        c_etype,
                        info.values.as_ptr(),
                        info.date_hour,
                        info.mtime,
                        longitudes,
                        latitudes,
                        info.width,
                        info.height,
                        step_idx,
                        cstr_chars(outf),
                    );
                    count += num;
                }
            }
            step_idx += 1;
        }
        (GLIB.free_grib_index)(cindex);
    } else {
        unsafe {
            let mi4dir = cstr_chars(mi4dir);
            let outf = cstr_chars(outf);
            count = (GLIB.mi4text_grib)(mi4dir, c_gribf, c_etype, date_hour, outf);
        }
    }
    print!("Mi4Text to Grib: {} - {} = {}\n", mi4dir, outf, count);
    return count;
}

/// MICAPS4 data directory write to GRIB2 file
/// # Examples
/// ```
/// use griblib_rust::gribtool;
///
/// let dir = "/data/mi4data/TMP/";
/// let etype = "TMP";
/// let date_hour = 2023021008;
/// let outf = "/data/grib2/tmp.GRB2";
/// let num = gribtool::mi4data_grib(dir, "", etype, date_hour, outf);
/// ```
pub fn mi4data_grib(mi4data: &str, gribf: &str, etype: &str, date_hour: u32, outf: &str) -> i32 {
    unsafe {
        let mi4text = cstr_chars(mi4data);
        let etype = cstr_chars(etype);
        let outf = cstr_chars(outf);
        let gribf = cstr_chars(gribf);
        let res = (GLIB.mi4data_grib)(mi4text, gribf, etype, date_hour, outf);
        print!("Mi4Text to Grib: {}\n", res);

        return res;
    }
}

#[cfg(test)]
pub mod tests {
    use super::*;

    #[test]
    fn test_load_lib() {
        GribLib::new("");
    }

    #[test]
    #[should_panic]
    fn test_load_error() {
        GribLib::new("./lib/");
    }
}