mod error;
pub use error::*;
use serde::Deserialize;
#[derive(Copy, Clone, Debug)]
pub enum ColorKind {
BlackAndWhite,
Original,
UniversalBlue,
Titan,
TheWeatherChannel,
Meteored,
NexradLevelIII,
RainbowSelexIS,
DarkSky,
}
impl From<ColorKind> for u32 {
fn from(color: ColorKind) -> Self {
match color {
ColorKind::BlackAndWhite => 0,
ColorKind::Original => 1,
ColorKind::UniversalBlue => 2,
ColorKind::Titan => 3,
ColorKind::TheWeatherChannel => 4,
ColorKind::Meteored => 5,
ColorKind::NexradLevelIII => 6,
ColorKind::RainbowSelexIS => 7,
ColorKind::DarkSky => 8,
}
}
}
#[derive(Copy, Clone, Debug)]
struct TileArguments {
size: u32,
x: u32,
y: u32,
zoom: u32,
color: ColorKind,
smooth: bool,
snow: bool,
}
#[derive(Copy, Clone, Debug)]
enum RequestArgumentsInner {
Tile(TileArguments),
}
#[derive(Copy, Clone)]
pub struct RequestArguments {
inner: RequestArgumentsInner,
}
impl RequestArguments {
pub fn new_tile(x: u32, y: u32, zoom: u32) -> Result<Self, error::ParameterError> {
let max_coord = 2u32.pow(zoom);
if x >= max_coord {
Err(ParameterError::XOutOfRange(
x,
format!(
"With a zoom of {}, the max value for x is {}",
zoom,
max_coord - 1
),
))
} else if y >= max_coord {
Err(ParameterError::YOutOfRange(
y,
format!(
"With a zoom of {}, the max value for y is {}",
zoom,
max_coord - 1
),
))
} else {
Ok(Self {
inner: RequestArgumentsInner::Tile(TileArguments {
size: 256,
x,
y,
zoom,
color: ColorKind::UniversalBlue,
smooth: true,
snow: true,
}),
})
}
}
pub fn set_size(&mut self, size: u32) -> Result<&mut Self, error::ParameterError> {
if size == 256 || size == 512 {
match &mut self.inner {
RequestArgumentsInner::Tile(tile) => {
tile.size = size;
}
};
Ok(self)
} else {
Err(ParameterError::InvalidSize(
size,
"Image size must be either 256 or 512".to_owned(),
))
}
}
pub fn set_smooth(&mut self, smooth: bool) -> &mut Self {
match &mut self.inner {
RequestArgumentsInner::Tile(tile) => {
tile.smooth = smooth;
}
};
self
}
pub fn set_snow(&mut self, snow: bool) -> &mut Self {
match &mut self.inner {
RequestArgumentsInner::Tile(tile) => {
tile.snow = snow;
}
};
self
}
pub fn set_color(&mut self, color: ColorKind) -> &mut Self {
match &mut self.inner {
RequestArgumentsInner::Tile(tile) => {
tile.color = color;
}
};
self
}
}
pub struct WeatherRequester {
client: reqwest::Client,
}
impl WeatherRequester {
pub fn new() -> Self {
Self {
client: reqwest::Client::new(),
}
}
pub async fn available(&self) -> Result<AvailableData, error::Error> {
let res = self
.client
.get("https://api.rainviewer.com/public/weather-maps.json")
.send()
.await?;
let raw: RawAvailableData = serde_json::from_str(res.text().await?.as_str())?;
Ok(AvailableData {
host: raw.host,
past_radar: raw.radar.past.into_iter().map(|r| r.into()).collect(),
nowcast_radar: raw.radar.nowcast.into_iter().map(|r| r.into()).collect(),
infrared_satellite: raw
.satellite
.infrared
.into_iter()
.map(|r| r.into())
.collect(),
})
}
pub async fn get_tile(
&self,
maps: &AvailableData,
frame: &Frame,
args: RequestArguments,
) -> Result<Vec<u8>, error::Error> {
match args.inner {
RequestArgumentsInner::Tile(args) => {
let options = format!("{}_{}", args.smooth as u8, args.snow as u8);
let color_val: u32 = args.color.into();
let url = format!(
"{}/{}/{}/{}/{}/{}/{}/{}.png",
maps.host, frame.path, args.size, args.zoom, args.x, args.y, color_val, options,
);
let res = self.client.get(url).send().await?;
match res.status() {
reqwest::StatusCode::OK => Ok(res.bytes().await?.to_vec()),
status => Err(Error::Http(status)),
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct Frame {
pub time: chrono::NaiveDateTime,
pub path: String,
}
#[derive(Debug, Clone)]
pub struct AvailableData {
host: String,
pub past_radar: Vec<Frame>,
pub nowcast_radar: Vec<Frame>,
pub infrared_satellite: Vec<Frame>,
}
#[derive(Deserialize)]
#[allow(dead_code)]
struct RawAvailableData {
pub version: String,
pub generated: u64,
pub host: String,
pub radar: Radar,
pub satellite: Satellite,
}
#[derive(Deserialize)]
struct Radar {
past: Vec<RawFrame>,
nowcast: Vec<RawFrame>,
}
#[derive(Deserialize)]
struct Satellite {
infrared: Vec<RawFrame>,
}
#[derive(Deserialize, Debug, Clone)]
struct RawFrame {
pub time: u64,
pub path: String,
}
impl From<RawFrame> for Frame {
fn from(raw: RawFrame) -> Self {
use chrono::TimeZone;
Self {
time: chrono::Utc.timestamp(raw.time as i64, 0).naive_utc(),
path: raw.path,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test() {
let req = WeatherRequester::new();
let maps = req.available().await.unwrap();
let frame = &maps.past_radar[0];
let args = RequestArgumentsInner::Tile(TileArguments {
size: 256,
x: 26,
y: 12,
zoom: 6,
color: ColorKind::UniversalBlue,
smooth: true,
snow: true,
});
let png = req
.get_tile(&maps, frame, RequestArguments { inner: args })
.await
.unwrap();
assert_eq!(&png[0..4], &[0x89, 0x50, 0x4e, 0x47]);
}
}