rss_core 0.5.0

Raster Source Service core library for querying, downloading, and processing remote sensing imagery
//! Cloud mask utilities with optional Python bindings via PyO3.

#[cfg(feature = "pyo3")]
use crate::{query::ImageQueryBuilder, qvf::QvfFilename, DEA};
#[cfg(feature = "pyo3")]
use pyo3::prelude::*;
#[cfg(feature = "pyo3")]
use pyo3::Bound;

#[cfg(feature = "pyo3")]
use std::path::PathBuf;

#[cfg(test)]
mod tests {
    // tests pending — imports were placeholders
}

#[cfg(feature = "pyo3")]
use anyhow::{bail, Result};

/// Downloads the Sentinel-2 cloudless mask for a given QVF scene from DEA.
///
/// Queries the DEA STAC catalog for the `oa_s2cloudless_mask` layer matching
/// the input QVF filename, downloads it, and renames to the output path.
///
/// # Arguments
///
/// * `in_qvf_fn` - Input QVF filename (e.g., `"cfmsre_t56jmr_20220104_abam5.img"`)
/// * `out_qvf_name` - Output filename for the cloudless mask
///
/// # Example
///
/// ```python
/// from rss_core import get_s2_cloudless_dea
/// get_s2_cloudless_dea("cfmsre_t56jmr_20220104_abam5.img", "t56jmr_cloudless.tif")
/// ```
#[cfg(feature = "pyo3")]
#[pyfunction]
pub fn get_s2_cloudless_dea(in_qvf_fn: &str, out_qvf_name: &str) -> Result<()> {
    // parse input
    let in_qvf_fn: QvfFilename = in_qvf_fn.parse().unwrap();
    // query dea base on the qvf_filename
    let source = DEA.clone();
    let query = ImageQueryBuilder::from_qvf(in_qvf_fn, source, ["oa_s2cloudless_mask"]).expect("Invalid query");

    // get the result of the query
    let dst = PathBuf::from(".");
    let q = query
        .get(&dst, None, None)
        .expect("Unable to download file");
    // We assume we will get only one item. Will be a problem, I know!
    if !q.items.is_empty() {
        let asset = &q.items[0].assets["oa_s2cloudless_mask"].href;

        // tidy up
        std::fs::rename(asset, dst.join(out_qvf_name)).expect("Could not create out file");
        std::fs::remove_dir_all(PathBuf::from(&asset).parent().unwrap())
            .expect("Unable to remove folder");
    } else {
        bail!("No image found.");
    }
    Ok(())
}

#[cfg(feature = "pyo3")]
#[pymodule]
fn rss_core(_py: Python, m: Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(get_s2_cloudless_dea, &m)?)?;
    Ok(())
}