Skip to main content

qualia_core_db/domains/geospatial/adapters/
opendap_adapter.rs

1use crate::domains::geospatial::adapters::{AdapterHttpRequest, DataAdapter};
2use crate::net::disclosure::NetworkDisclosureRegistry;
3
4/// Adapter for Open-source Project for a Network Data Access Protocol (OPeNDAP) endpoints.
5/// Primarily used for subsetting massive 4D/5D NetCDF arrays (e.g. atmospheric layers, ocean currents).
6pub struct OpendapAdapter {
7    pub id: &'static str,
8    pub endpoint: String,
9    pub dataset_id: String,
10}
11
12impl OpendapAdapter {
13    pub fn new(id: &'static str, endpoint: &str, dataset_id: &str) -> Self {
14        Self {
15            id,
16            endpoint: endpoint.to_string(),
17            dataset_id: dataset_id.to_string(),
18        }
19    }
20}
21
22impl DataAdapter for OpendapAdapter {
23    fn adapter_id(&self) -> &'static str {
24        self.id
25    }
26
27    fn build_fetch_request(
28        &self,
29        _bbox: (f64, f64, f64, f64),
30        _time_range: (u64, u64),
31        registry: &NetworkDisclosureRegistry,
32    ) -> Result<AdapterHttpRequest, String> {
33        if !registry.check_egress_consent(self.adapter_id(), &self.endpoint) {
34            return Err(format!(
35                "Consent denied or unregistered for OPeNDAP endpoint {}",
36                self.endpoint
37            ));
38        }
39
40        // Construct OPeNDAP constraint expression assuming standard lat/lon mapping for a spatial subset.
41        // Example: ?variable[lat_idx_min:1:lat_idx_max][lon_idx_min:1:lon_idx_max]
42        // Since we don't have the DDS to resolve indices here, we fetch the DDS endpoint to verify access.
43        let dds_url = format!("{}.dds", self.endpoint);
44
45        Ok(AdapterHttpRequest::get(dds_url, "OPeNDAP"))
46    }
47
48    fn primary_endpoint(&self) -> &str {
49        &self.endpoint
50    }
51
52    fn estimate_tile_count(&self, _bbox: (f64, f64, f64, f64)) -> u32 {
53        // Typically a DDS fetch followed by a targeted subset request
54        2
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_opendap_adapter_consent_denied() {
64        let registry = NetworkDisclosureRegistry::new();
65        let adapter = OpendapAdapter::new(
66            "opendap_adapter",
67            "https://cds.climate.copernicus.eu/api",
68            "era5",
69        );
70
71        let res = adapter.fetch_region((0.0, 0.0, 1.0, 1.0), (0, 0), &registry);
72        assert!(res.is_err());
73    }
74}