use crate::field::ProfileField;
use crate::grib::profile_from_grib2;
use crate::{Error, Result};
use std::io::Read;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Model {
Gfs,
}
#[derive(Debug, Clone)]
pub struct NomadsRequest {
pub model: Model,
pub date_utc: u32,
pub cycle_hour_utc: u32,
pub forecast_hour: u32,
pub latitude_deg: f64,
pub longitude_deg: f64,
pub levels_mb: Vec<u32>,
pub launch_azimuth_deg: f64,
}
impl NomadsRequest {
pub fn url(&self) -> String {
let Model::Gfs = self.model;
let mut url = format!(
"https://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_0p25.pl?dir=%2Fgfs.{}%2F{:02}%2Fatmos&file=gfs.t{:02}z.pgrb2.0p25.f{:03}",
self.date_utc, self.cycle_hour_utc, self.cycle_hour_utc, self.forecast_hour
);
for level in &self.levels_mb {
url.push_str(&format!("&lev_{level}_mb=on"));
}
url.push_str("&var_HGT=on&var_UGRD=on&var_VGRD=on&subregion=");
url.push_str(&format!(
"&leftlon={}&rightlon={}&toplat={}&bottomlat={}",
self.longitude_deg - 1.5,
self.longitude_deg + 1.5,
self.latitude_deg + 1.5,
self.latitude_deg - 1.5
));
url
}
}
pub fn fetch_profile(request: &NomadsRequest) -> Result<ProfileField> {
let url = request.url();
let response = ureq::get(&url)
.call()
.map_err(|e| Error::invalid("download", format!("request failed: {e}")))?;
let mut bytes = Vec::new();
response
.into_body()
.into_reader()
.read_to_end(&mut bytes)
.map_err(|e| Error::invalid("download", format!("read failed: {e}")))?;
if !bytes.starts_with(b"GRIB") {
return Err(Error::invalid(
"download",
"response is not GRIB2 (bad cycle or unavailable data)",
));
}
profile_from_grib2(
&bytes,
request.latitude_deg,
request.longitude_deg,
request.launch_azimuth_deg,
)
}