use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc};
use rgb::RGB;
use spa::{SolarPos, SpaError};
use crate::{
source::{ColorBrightness}, light::{GlobalLighting, pitch_to_rgb}, map::Angles,
};
impl Angles {
pub(crate) fn from_solar_pos(pos: SolarPos) -> Self {
let pitch = pos.zenith_angle - 90.0;
let yaw = (270.0 - pos.azimuth).rem_euclid(360.0);
let roll = 0.0;
Angles { pitch, yaw, roll }
}
}
pub trait DateTimeUtcExt {
fn from_longitude(longitude_east: f64, datetime: NaiveDateTime) -> Option<DateTime<Utc>> {
let offset = lon_to_offset(longitude_east)?;
let datetime_fixed = DateTime::<FixedOffset>::from_local(datetime, offset);
Some(datetime_fixed.into())
}
}
impl DateTimeUtcExt for DateTime<Utc> {}
pub trait NaiveDateTimeExt {
fn from_season(season: Season, time: NaiveTime) -> Option<NaiveDateTime> {
let year = 2023;
let (month, day) = match season {
Season::Spring => (3, 24), Season::Summer => (6, 25), Season::Fall => (9, 25), Season::Winter => (12, 24), };
Some(NaiveDate::from_ymd_opt(year, month, day)?.and_time(time))
}
}
impl NaiveDateTimeExt for NaiveDateTime {}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Season {
Spring,
Summer,
Fall,
Winter,
}
pub(crate) fn lon_to_offset(longitude_east: f64) -> Option<FixedOffset> {
let tz_hour = longitude_east / 15.0;
let tz_secs = (tz_hour * 3600.0).round() as i32;
FixedOffset::east_opt(tz_secs)
}
pub fn calc_solar_position_local(
lat: f64,
lon: f64,
datetime: NaiveDateTime,
) -> Result<SolarPos, SpaError> {
let utc = DateTime::from_longitude(lon, datetime).ok_or(SpaError::BadParam)?;
spa::calc_solar_position(utc, lat, lon)
}
pub fn loc_time_to_sun(
lat: f64,
lon: f64,
datetime: NaiveDateTime,
) -> Result<GlobalLighting, SpaError> {
let solar_pos = calc_solar_position_local(lat, lon, datetime)?;
let mut sun_dir = Angles::from_solar_pos(solar_pos);
dbg!(sun_dir.pitch);
let RGB { r, g, b } = pitch_to_rgb(-sun_dir.pitch);
sun_dir.pitch = -sun_dir.pitch.abs();
Ok(GlobalLighting {
sun_color: ColorBrightness::new(r, g, b, 255), sun_dir: sun_dir.clone(),
amb_color: ColorBrightness::new(171, 206, 220, 50), amb_dir: sun_dir,
dir_lights: Vec::new(),
})
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn loc_time() {
let datetime =
NaiveDate::from_ymd_opt(2023, 4, 21).unwrap().and_hms_opt(14, 42, 0).unwrap();
println!("datetime: {}", datetime);
let lighting = loc_time_to_sun(36.188110, -115.176468, datetime).unwrap();
let dir = &lighting.sun_dir;
dbg!(dir);
assert_relative_eq!(-46.0, dir.pitch, epsilon = 3.0);
assert_relative_eq!(22.0, dir.yaw, epsilon = 3.0);
assert_relative_eq!(0.0, dir.roll);
}
}