oceansdb 0.0.1

A database of marine reference data such as climatologies and bathymetry
Documentation
use std::io::{Read, Write};

use dirs;
use netcdf;

type Error = Box<dyn std::error::Error>;

use std::path::PathBuf;

fn data_dir() -> Result<PathBuf, Error> {
    let mut path = dirs::cache_dir().unwrap();
    path.push("oceansdb");
    // TODO: in the future might want to avoid create_dir and just return path?
    std::fs::create_dir_all(&path)?;
    Ok(path)
}

// Interpolate (time, lat, lon, depth)
//   - First approximation the closest point
// Can be multiple variables, such as temp, salt, depth, ...
fn etopo() -> Result<String, Error> {
    let url = "https://pae-paha.pacioos.hawaii.edu/thredds/ncss/etopo5?var=ROSE&disableLLSubset=on&disableProjSubset=on&horizStride=1";
    // let hash = "385c91f318e51a95bfb3213abc45bfe6";
    let mut p = data_dir().unwrap();
    p.push("etopo5.nc");

    if !p.exists() {
        download(url, p.to_str().unwrap()).unwrap();
    }

    Ok(p.to_str().unwrap().to_string())
}

fn download(url: &str, filename: &str) -> Result<usize, Error> {
    //let url = "https://www.ngdc.noaa.gov/thredds/fileServer/global/ETOPO2022/60s/60s_bed_elev_netcdf/ETOPO_2022_v1_60s_N90W180_bed.nc";
    //let hash = "e7e7efb75230280126bc96e910f71010";
    // 491284376
    //let url = "https://www.ngdc.noaa.gov/mgg/global/relief/ETOPO2/ETOPO2v2-2006/ETOPO2v2g/netCDF/ETOPO2v2g_f4_netCDF.zip";

    let mut fp = std::io::BufWriter::new(
        std::fs::OpenOptions::new()
            .create(true)
            .write(true)
            .open(filename)?,
    );

    let mut res = ureq::get(url).call()?.into_reader().take(500_000_000);
    let mut buffer = vec![];
    res.read_to_end(&mut buffer)?;
    let s = fp.write(buffer.as_slice())?;
    dbg!(s);
    //while let Some(chunk) = res.chunk().await? {
    Ok(s)
}

pub struct Etopo5 {}

impl Etopo5 {
    // maybe do "skip_check" option here? Better to check by default, and give a escape hatch?
    pub fn new() -> Self {
        // init: check if file exist, download if not. No other arguments
        //   let file = netcdf::open("woa18_decav_t13_5d.nc").unwrap();
        //   dbg!(&file.variable("lat"));
        // future: cache variables/chunks? Maybe use once_cell::Lazy,
        //         function know how to read from file,
        //         and we can unload data and reload on access
        Self {}
    }

    // validate after opening (avoid doing it on init because might be heavy?)
    // fn check() {}

    // Case for a single point. Return the closest to the given location.

    /// # Example
    /// ```
    /// use oceansdb::Etopo5;
    /// let bathymetry = Etopo5::new();
    /// let depth = bathymetry.depth(15.0, 322.0);
    /// assert_eq!(depth, -5017.0);
    /// ```
    pub fn depth(&self, lat: f32, lon: f32) -> f32 {
        // depth_range -> need interpolation, (start, end) points for lat/lon
        let filename = etopo().unwrap();
        let file = netcdf::open(filename).unwrap();
        let var_lat = &file.variable("ETOPO05_Y").unwrap();
        let lat_data = var_lat.values_arr::<f64, _>(..).unwrap();
        dbg!(&lat_data);
        let var_lon = &file.variable("ETOPO05_X").unwrap();
        let lon_data = var_lon.values_arr::<f64, _>(..).unwrap();
        dbg!(&lon_data);

        //let mane = lat_data.iter().min_by(|&a, &b| {(a - lat).abs().cmp(&(b - lat as f64).abs())}).iter().min();

        let dx = lon_data.map(|x| (x - lon as f64).abs());
        let mut i = 0;
        for (n, &value) in dx.iter().enumerate() {
            if value < dx[i] {
                i = n;
            }
        }
        // dbg!(&i, dx[i], lon_data[i]);

        let dy = lat_data.map(|y| (y - lat as f64).abs());
        let mut j = 0;
        for (n, &value) in dy.iter().enumerate() {
            if value < dy[j] {
                j = n;
            }
        }
        // dbg!(&j, dy[j], lat_data[j]);

        //.min_by(|a, b| a[1].partial_cmp(b[1]).expect("Tried to compare a NaN"));
        let depth = &file
            .variable("ROSE")
            .unwrap()
            .value::<f32, _>([j, i])
            .unwrap();
        dbg!(&depth);

        *depth
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    //use futures::executor::block_on;

    /*
    #[test]
    fn test_download() {
        let s = block_on(download()).unwrap();
        //dbg!("Hey Hoooo!");
        dbg!(s);
        assert_eq!(s, 101926074);
    }
    */

    #[test]
    // This is a small file, so a good choice to test downloading everytime.
    fn download_etopo() {
        let filename = etopo().unwrap();
        assert!(PathBuf::from(filename).exists());
    }

    // Start test
    //   - init Etopo5
    //   - run depth with one lat/lon
    #[test]
    fn bathymetry() {
        let etopo = Etopo5::new();
        let depth = etopo.depth(15.0, 360.0 - 38.0);
        assert_eq!(depth, -5017.0);
    }
}