Skip to content

Commit

Permalink
block[weather]: Add sunrise/sunset to nws
Browse files Browse the repository at this point in the history
  • Loading branch information
bim9262 committed Nov 11, 2024
1 parent b77f328 commit 11b0921
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 93 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ shellexpand = "3.0"
signal-hook = "0.3"
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
smart-default = "0.7"
sunrise-next = "1.2.3"
swayipc-async = "2.0"
thiserror = "1.0"
toml = { version = "0.8", features = ["preserve_order"] }
Expand Down
43 changes: 23 additions & 20 deletions src/blocks/weather.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
//! `wind{,_{favg,fmin,fmax,ffin}}` | Wind speed | Number | -
//! `wind_kmh{,_{favg,fmin,fmax,ffin}}` | Wind speed. The wind speed in km/h | Number | -
//! `direction{,_{favg,fmin,fmax,ffin}}` | Wind direction, e.g. "NE" | Text | -
//! `sunrise` | Time of sunrise | DateTime | -
//! `sunrise` | Time of sunrise | DateTime | -
//! `sunset` | Time of sunset | DateTime | -
//!
//! You can use the suffixes noted above to get the following:
Expand Down Expand Up @@ -145,7 +145,8 @@ use std::fmt;
use std::sync::{Arc, Mutex};
use std::time::Instant;

use chrono::{DateTime, Utc};
use chrono::{DateTime, Datelike, Utc};
use sunrise::{SolarDay, SolarEvent};

use crate::formatting::Format;

Expand Down Expand Up @@ -183,7 +184,6 @@ trait WeatherProvider {
&self,
autolocated_location: Option<&Coordinates>,
need_forecast: bool,
need_sunrise_and_sunset: bool,
) -> Result<WeatherResult>;
}

Expand Down Expand Up @@ -271,8 +271,8 @@ struct WeatherResult {
location: String,
current_weather: WeatherMoment,
forecast: Option<Forecast>,
sunrise: Option<DateTime<Utc>>,
sunset: Option<DateTime<Utc>>,
sunrise: DateTime<Utc>,
sunset: DateTime<Utc>,
}

impl WeatherResult {
Expand All @@ -289,6 +289,8 @@ impl WeatherResult {
"wind" => Value::number(self.current_weather.wind),
"wind_kmh" => Value::number(self.current_weather.wind_kmh),
"direction" => Value::text(convert_wind_direction(self.current_weather.wind_direction).into()),
"sunrise" => Value::datetime(self.sunrise, None),
"sunset" => Value::datetime(self.sunset, None),
};

if let Some(forecast) = self.forecast {
Expand Down Expand Up @@ -320,13 +322,6 @@ impl WeatherResult {
}
}

if let Some(sunset) = self.sunset {
values.insert("sunset".into(), Value::datetime(sunset, None));
}

if let Some(sunrise) = self.sunrise {
values.insert("sunrise".into(), Value::datetime(sunrise, None));
}
values
}
}
Expand Down Expand Up @@ -465,7 +460,6 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {

let autolocate_interval = config.autolocate_interval.unwrap_or(config.interval);
let need_forecast = need_forecast(&format, format_alt.as_ref());
let need_sunrise_and_sunset = need_sunrise_and_sunset(&format, format_alt.as_ref());

let mut timer = config.interval.timer();

Expand All @@ -477,8 +471,7 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
None
};

let fetch =
|| provider.get_weather(location.as_ref(), need_forecast, need_sunrise_and_sunset);
let fetch = || provider.get_weather(location.as_ref(), need_forecast);
let data = fetch.retry(ExponentialBuilder::default()).await?;
let data_values = data.into_values();

Expand Down Expand Up @@ -527,11 +520,21 @@ fn need_forecast(format: &Format, format_alt: Option<&Format>) -> bool {
has_forecast_key(format) || format_alt.is_some_and(has_forecast_key)
}

fn need_sunrise_and_sunset(format: &Format, format_alt: Option<&Format>) -> bool {
fn has_sunrise_or_sunset_keys(format: &Format) -> bool {
format.contains_key("sunrise") || format.contains_key("sunset")
}
has_sunrise_or_sunset_keys(format) || format_alt.is_some_and(has_sunrise_or_sunset_keys)
fn calculate_sunrise_sunset(
lat: f64,
lon: f64,
altitude: Option<f64>,
) -> Result<(DateTime<Utc>, DateTime<Utc>)> {
let date = Utc::now();
let solar_day = SolarDay::new(lat, lon, date.year(), date.month(), date.day())
.with_altitude(altitude.unwrap_or_default());

Ok((
DateTime::<Utc>::from_timestamp(solar_day.event_time(SolarEvent::Sunrise), 0)
.error("Unable to convert timestamp to DateTime")?,
DateTime::<Utc>::from_timestamp(solar_day.event_time(SolarEvent::Sunset), 0)
.error("Unable to convert timestamp to DateTime")?,
))
}

#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq, SmartDefault)]
Expand Down
67 changes: 14 additions & 53 deletions src/blocks/weather/met_no.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,42 +171,36 @@ struct ForecastTimeInstant {
relative_humidity: Option<f64>,
}

#[derive(Debug, Deserialize)]
struct SunResponse {
properties: Properties,
}

#[derive(Debug, Deserialize)]
struct Properties {
sunrise: TimeData,
sunset: TimeData,
}

#[derive(Debug, Deserialize)]
struct TimeData {
time: String,
}

static LEGENDS: LazyLock<Option<LegendsStore>> =
LazyLock::new(|| serde_json::from_str(include_str!("met_no_legends.json")).ok());

const FORECAST_URL: &str = "https://api.met.no/weatherapi/locationforecast/2.0/compact";
const SUN_URL: &str = "https://api.met.no/weatherapi/sunrise/3.0/sun";

#[async_trait]
impl WeatherProvider for Service<'_> {
async fn get_weather(
&self,
location: Option<&Coordinates>,
need_forecast: bool,
need_sunrise_and_sunset: bool,
) -> Result<WeatherResult> {
let (lat, lon) = location
.as_ref()
.map(|loc| (loc.latitude.to_string(), loc.longitude.to_string()))
.or_else(|| self.config.coordinates.clone())
.error("No location given")?;

let altitude = if let Some(altitude) = &self.config.altitude {
Some(altitude.parse().error("Unable to convert string to f64")?)
} else {
None
};

let (sunrise, sunset) = calculate_sunrise_sunset(
lat.parse().error("Unable to convert string to f64")?,
lon.parse().error("Unable to convert string to f64")?,
altitude,
)?;

let querystr: HashMap<&str, String> = map! {
"lat" => &lat,
"lon" => &lon,
Expand Down Expand Up @@ -234,6 +228,8 @@ impl WeatherProvider for Service<'_> {
location: location_name,
current_weather,
forecast: None,
sunrise,
sunset,
});
}

Expand All @@ -255,41 +251,6 @@ impl WeatherProvider for Service<'_> {

let forecast = Some(Forecast::new(&data_agg, fin));

let (sunset, sunrise) = match need_sunrise_and_sunset {
true => {
let sun_query_string: HashMap<&str, String> = map! {
"lat" => &lat,
"lon" => &lon,
};

let sun_data: SunResponse = REQWEST_CLIENT
.get(SUN_URL)
.query(&sun_query_string)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.send()
.await
.error("Forecast request failed")?
.json()
.await
.error("Forecast request failed")?;

let sunset = Some(
DateTime::parse_from_str(&sun_data.properties.sunset.time, "%Y-%m-%dT%H:%M%z")
.error("failed to parse sunset timestring")?
.to_utc(),
);

let sunrise = Some(
DateTime::parse_from_str(&sun_data.properties.sunrise.time, "%Y-%m-%dT%H:%M%z")
.error("failed to parse sunrise timestring")?
.to_utc(),
);

(sunset, sunrise)
}
false => (None, None),
};

Ok(WeatherResult {
location: location_name,
current_weather,
Expand Down
33 changes: 24 additions & 9 deletions src/blocks/weather/nws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub struct Config {
struct LocationInfo {
query: String,
name: String,
lat: f64,
lon: f64,
}

pub(super) struct Service<'a> {
Expand All @@ -45,12 +47,19 @@ impl<'a> Service<'a> {
None
} else {
let coords = config.coordinates.as_ref().error("no location given")?;
Some(Self::get_location_query(&coords.0, &coords.1, config.units).await?)
Some(
Self::get_location_query(
coords.0.parse().error("Unable to convert string to f64")?,
coords.1.parse().error("Unable to convert string to f64")?,
config.units,
)
.await?,
)
};
Ok(Self { config, location })
}

async fn get_location_query(lat: &str, lon: &str, units: UnitSystem) -> Result<LocationInfo> {
async fn get_location_query(lat: f64, lon: f64, units: UnitSystem) -> Result<LocationInfo> {
let points_url = format!("{API_URL}/points/{lat},{lon}");

let response: ApiPoints = REQWEST_CLIENT
Expand All @@ -68,7 +77,12 @@ impl<'a> Service<'a> {
});
let location = response.properties.relative_location.properties;
let name = format!("{}, {}", location.city, location.state);
Ok(LocationInfo { query, name })
Ok(LocationInfo {
query,
name,
lat,
lon,
})
}
}

Expand Down Expand Up @@ -233,16 +247,13 @@ impl WeatherProvider for Service<'_> {
need_forecast: bool,
) -> Result<WeatherResult> {
let location = if let Some(coords) = autolocated {
Self::get_location_query(
&coords.latitude.to_string(),
&coords.longitude.to_string(),
self.config.units,
)
.await?
Self::get_location_query(coords.latitude, coords.longitude, self.config.units).await?
} else {
self.location.clone().error("No location was provided")?
};

let (sunrise, sunset) = calculate_sunrise_sunset(location.lat, location.lon, None)?;

let data: ApiForecastResponse = REQWEST_CLIENT
.get(location.query)
.header(
Expand All @@ -264,6 +275,8 @@ impl WeatherProvider for Service<'_> {
location: location.name,
current_weather,
forecast: None,
sunrise,
sunset,
});
}

Expand All @@ -281,6 +294,8 @@ impl WeatherProvider for Service<'_> {
location: location.name,
current_weather,
forecast,
sunrise,
sunset,
})
}
}
Expand Down
19 changes: 8 additions & 11 deletions src/blocks/weather/open_weather_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ impl WeatherProvider for Service<'_> {
&self,
autolocated: Option<&Coordinates>,
need_forecast: bool,
_need_sunrise_and_sunset: bool,
) -> Result<WeatherResult> {
let location_query = autolocated
.as_ref()
Expand Down Expand Up @@ -276,11 +275,19 @@ impl WeatherProvider for Service<'_> {

let current_weather = current_data.to_moment(self.units);

let sunrise = DateTime::<Utc>::from_timestamp(current_data.sys.sunrise, 0)
.error("Unable to convert timestamp to DateTime")?;

let sunset = DateTime::<Utc>::from_timestamp(current_data.sys.sunset, 0)
.error("Unable to convert timestamp to DateTime")?;

if !need_forecast || self.forecast_hours == 0 {
return Ok(WeatherResult {
location: current_data.name,
current_weather,
forecast: None,
sunrise,
sunset,
});
}

Expand Down Expand Up @@ -320,16 +327,6 @@ impl WeatherProvider for Service<'_> {

let forecast = Some(Forecast::new(&data_agg, fin));

let sunrise = Some(
DateTime::<Utc>::from_timestamp(current_data.sys.sunrise, 0)
.error("Unable to convert timestamp to DateTime")?,
);

let sunset = Some(
DateTime::<Utc>::from_timestamp(current_data.sys.sunset, 0)
.error("Unable to convert timestamp to DateTime")?,
);

Ok(WeatherResult {
location: current_data.name,
current_weather,
Expand Down

0 comments on commit 11b0921

Please sign in to comment.