Created
July 9, 2020 13:14
-
-
Save vineetred/9e20d15e8ea1313878fb5071b8e97c14 to your computer and use it in GitHub Desktop.
Solar Timetable in Rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// This file is a copy of [solar.c](https://github.com/jonls/redshift/blob/master/src/solar.c) from redshift. | |
/// | |
/// This module makes extensive use of the [Julian Day notation](https://en.wikipedia.org/wiki/Julian_day) | |
/// to measure elapsed days between events and in calculations. | |
use std::collections::HashMap; | |
use chrono; | |
use chrono::prelude::*; | |
/* Ported from javascript code by U.S. Department of Commerce, | |
National Oceanic & Atmospheric Administration: | |
http://www.srrb.noaa.gov/highlights/sunrise/calcdetails.html | |
It is based on equations from "Astronomical Algorithms" by | |
Jean Meeus. */ | |
/// Model of atmospheric refraction near horizon (in degrees). | |
const ATM_REFRAC: f64 = 0.833; | |
const ASTRO_TWILIGHT_ELEV: f64 = -18.0; | |
const NAUT_TWILIGHT_ELEV: f64 = -12.0; | |
const CIVIL_TWILIGHT_ELEV: f64 = -6.0; | |
const DAYTIME_ELEV: f64 = (0.0 - ATM_REFRAC); | |
/// Representation of various times of day in the solar cycle | |
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone, Debug)] | |
pub enum SolarTime { | |
Noon = 0, | |
Midnight, | |
AstroDawn, | |
NautDawn, | |
CivilDawn, | |
Sunrise, | |
Sunset, | |
CivilDusk, | |
NautDusk, | |
AstroDusk, | |
} | |
impl SolarTime { | |
pub fn iterator() -> impl Iterator<Item = SolarTime> { | |
[ | |
SolarTime::Noon, | |
SolarTime::Midnight, | |
SolarTime::AstroDawn, | |
SolarTime::NautDawn, | |
SolarTime::CivilDawn, | |
SolarTime::Sunrise, | |
SolarTime::Sunset, | |
SolarTime::CivilDusk, | |
SolarTime::NautDusk, | |
SolarTime::AstroDusk, | |
] | |
.iter() | |
.copied() | |
} | |
} | |
fn generate_time_angles() -> HashMap<SolarTime, f64> { | |
let mut ret: HashMap<SolarTime, f64> = HashMap::new(); | |
ret.insert( | |
SolarTime::AstroDawn, | |
(-90.0 + ASTRO_TWILIGHT_ELEV).to_radians(), | |
); | |
ret.insert( | |
SolarTime::NautDawn, | |
(-90.0 + NAUT_TWILIGHT_ELEV).to_radians(), | |
); | |
ret.insert( | |
SolarTime::CivilDawn, | |
(-90.0 + CIVIL_TWILIGHT_ELEV).to_radians(), | |
); | |
ret.insert(SolarTime::Sunrise, (-90.0 + DAYTIME_ELEV).to_radians()); | |
ret.insert(SolarTime::Noon, 0.0_f64.to_radians()); | |
ret.insert(SolarTime::Sunset, (90.0 - DAYTIME_ELEV).to_radians()); | |
ret.insert( | |
SolarTime::CivilDusk, | |
(90.0 - CIVIL_TWILIGHT_ELEV).to_radians(), | |
); | |
ret.insert( | |
SolarTime::NautDusk, | |
(90.0 - NAUT_TWILIGHT_ELEV).to_radians(), | |
); | |
ret.insert( | |
SolarTime::AstroDusk, | |
(90.0 - ASTRO_TWILIGHT_ELEV).to_radians(), | |
); | |
ret | |
} | |
/// Calculates the Unix epoch from a Julian day | |
/// - jd: a Julian day | |
fn epoch_from_jd(jd: f64) -> f64 { | |
86400.0 * (jd - 2440587.5) | |
} | |
/// Calculates the Julian day from a Unix epoch | |
/// - t: a Unix epoch in seconds | |
fn jd_from_epoch(t: f64) -> f64 { | |
(t / 86400.0) + 2440587.5 | |
} | |
/// Calculates the number of Julian centuries since J2000.0 from a Julian day | |
/// - jd: a Julian day | |
fn jcent_from_jd(jd: f64) -> f64 { | |
(jd - 2451545.0) / 36525.0 | |
} | |
/// Calculates a Julian day from the number of Julian centuries since J2000.0 | |
/// - t: Julian centuries since J2000.0 | |
fn jd_from_jcent(t: f64) -> f64 { | |
36525.0 * t + 2451545.0 | |
} | |
/// Calculates the geometric mean longitude of the sun (in radians). | |
/// The mean logitude of the sun is the ecliptic longitude at wich it would be | |
/// if its orbit was perfectly circular | |
/// - t: Julian centuries since J2000.0 | |
fn sun_geom_mean_lon(t: f64) -> f64 { | |
let ret: f64 = 280.46646 + t * (36000.76983 + t * 0.0003032); | |
ret.rem_euclid(360.0).to_radians() | |
} | |
/// Calculates the geometric mean anomaly of the sun (in radians). | |
/// The mean anomaly is the fraction of the sun's period that has elapsed | |
/// since the sun passed periapsis | |
/// - t: Julian centuries since J2000.0 | |
fn sun_geom_mean_anomaly(t: f64) -> f64 { | |
let ret: f64 = 357.52911 + t * (35999.05029 - t * 0.0001537); | |
ret.to_radians() | |
} | |
/// Calculates the eccentricity of the Earth's orbit: | |
/// returns a parameter from 0 to 1 that determines the amount by which | |
/// its orbit around the sun deviates from a perfect circle | |
/// - t: Julian centuries since J2000.0 | |
fn earth_orbit_eccentricity(t: f64) -> f64 { | |
0.016708634 - t * (0.000042037 + t * 0.0000001267) | |
} | |
/// Calculates the result of the equation of the center for the sun's orbit, | |
/// which consists in the difference between the sun's position in its elliptical orbit and | |
/// its position in a circular one, or just the difference between true anomaly and mean anomaly | |
/// - t: Julian centuries since J2000.0 | |
fn sun_equation_of_center(t: f64) -> f64 { | |
let ma: f64 = sun_geom_mean_anomaly(t); | |
let center: f64 = ma.sin() * (1.914602 - t * (0.004817 + 0.000014 * t)) | |
+ (ma * 2.0).sin() * (0.019993 - 0.000101 * t) | |
+ (ma * 3.0).sin() * 0.000289; | |
center.to_radians() | |
} | |
// Calculates the true longitude of the sun in the elliptical orbit | |
/// - t: Julian centuries since J2000.0 | |
fn sun_true_lon(t: f64) -> f64 { | |
sun_geom_mean_lon(t) + sun_equation_of_center(t) | |
} | |
/// Calculates the apparent longitude of the sun (right ascension) | |
/// - t: Julian centuries since J2000.0 | |
fn sun_apparent_lon(t: f64) -> f64 { | |
let term: f64 = 125.04 - 1934.136 * t; | |
let true_lon: f64 = sun_true_lon(t); | |
let ret: f64 = true_lon.to_degrees() - 0.00569 - 0.00478 * term.to_radians().sin(); | |
ret.to_radians() | |
} | |
/// Calculates the mean obliquity/axial tilt of the Earth's orbit | |
/// - t: Julian centuries since J2000.0 | |
fn mean_ecliptic_obliquity(t: f64) -> f64 { | |
let sec: f64 = 21.448 - t * (46.815 + t * (0.00059 - t * 0.001813)); | |
let ret: f64 = 23.0 + (26.0 + (sec / 60.0)) / 60.0; | |
ret.to_radians() | |
} | |
/// Calculates the corrected obliquity/axial tilt of the Earth's orbit | |
/// - t: Julian centuries since J2000.0 | |
fn obliquity_corrected(t: f64) -> f64 { | |
let e_0: f64 = mean_ecliptic_obliquity(t); | |
let omega: f64 = 125.04 - t * 1934.136; | |
let ret: f64 = e_0.to_degrees() + (0.00256 * omega.to_radians().cos()); | |
ret.to_radians() | |
} | |
/// Calculates the declination (in radians) of the sun's orbit | |
/// - t: Julian centuries since J2000.0 | |
fn solar_declination(t: f64) -> f64 { | |
let e: f64 = obliquity_corrected(t); | |
let lambda: f64 = sun_apparent_lon(t); | |
let ret: f64 = e.sin() * lambda.sin(); | |
ret.asin() | |
} | |
/// Calculates the difference (in minutes) between true solar time and mean solar time | |
/// - t: Julian centuries since J2000.0 | |
fn equation_of_time(t: f64) -> f64 { | |
let epsilon: f64 = obliquity_corrected(t); | |
let l_0: f64 = sun_geom_mean_lon(t); | |
let e: f64 = earth_orbit_eccentricity(t); | |
let m: f64 = sun_geom_mean_anomaly(t); | |
let y: f64 = (epsilon / 2.0).tan().powi(2); | |
let eq_result: f64 = y * (l_0 * 2.0).sin() - 2.0 * e * m.sin() | |
+ 4.0 * e * y * m.sin() * (l_0 * 2.0).cos() | |
- 0.5 * y.powi(2) * (l_0 * 4.0).sin() | |
- 1.25 * e.powi(2) * (m * 2.0).sin(); | |
4.0 * eq_result.to_degrees() | |
} | |
/// Calculates the hour angle (in radians) at the location for the given angular elevation. | |
/// - lat: Latitude of location in degrees | |
/// - decl: Declination in radians | |
/// - elev: Angular elevation angle in radians | |
fn hour_angle_from_elevation(lat: f64, decl: f64, elev: f64) -> f64 { | |
let term: f64 = (elev.abs().cos() - lat.to_radians().sin() * decl.sin()) | |
/ (lat.to_radians().cos() * decl.cos()); | |
let omega: f64 = term.acos(); | |
omega.copysign(-elev) | |
} | |
/// Calculates the hour angle (in radians) at the location for the given angular elevation. | |
/// - lat: Latitude of location in degrees | |
/// - decl: Declination in radians | |
/// - ha: Hour angle in radians | |
fn elevation_from_hour_angle(lat: f64, decl: f64, ha: f64) -> f64 { | |
let ret: f64 = | |
ha.cos() * lat.to_radians().cos() * decl.cos() + lat.to_radians().sin() * decl.sin(); | |
ret.asin() | |
} | |
/// Calculates the time of apparent solar noon of a location on Earth. | |
/// Returns the time difference from mean solar midnight in minutes. | |
/// - t: Julian centuries since J2000.0 | |
/// - lon: Longitude of location in degrees | |
fn time_of_solar_noon(t: f64, lon: f64) -> f64 { | |
// First pass uses approximate solar noon to calculate equation of time. | |
let t_noon: f64 = jcent_from_jd(jd_from_jcent(t) - lon / 360.0); | |
let eq_time: f64 = equation_of_time(t_noon); | |
let sol_noon: f64 = 720.0 - 4.0 * lon - eq_time; | |
// Recalculate using new solar noon | |
let t_noon_adj: f64 = jcent_from_jd(jd_from_jcent(t) - 0.5 + sol_noon / 1440.0); | |
let eq_time_adj: f64 = equation_of_time(t_noon_adj); | |
let sol_noon_adj: f64 = 720.0 - 4.0 * lon - eq_time_adj; | |
sol_noon_adj | |
} | |
/// Calculates the time of given apparent solar angular elevation of location on earth. | |
/// Returns the time difference from mean solar midnight in minutes. | |
/// - t: Julian centuries since J2000.0 | |
/// - t_noon: Apparent solar noon in Julian centuries since J2000.0 | |
/// - lat: Latitude of location in degrees | |
/// - lon: Longtitude of location in degrees | |
/// - elev: Solar angular elevation in radians | |
fn time_of_solar_elevation(t: f64, t_noon: f64, lat: f64, lon: f64, elev: f64) -> f64 { | |
// First pass uses approximate sunrise to calculate equation of time | |
let eq_time: f64 = equation_of_time(t_noon); | |
let sol_decl: f64 = solar_declination(t_noon); | |
let ha: f64 = hour_angle_from_elevation(lat, sol_decl, elev); | |
let sol_offset: f64 = 720.0 - 4.0 * (lon + ha.to_degrees()) - eq_time; | |
// Recalculate using new sunrise | |
let t_rise: f64 = jcent_from_jd(jd_from_jcent(t) + sol_offset / 1440.0); | |
let eq_time_adj: f64 = equation_of_time(t_rise); | |
let sol_decl_adj: f64 = solar_declination(t_rise); | |
let ha_adj: f64 = hour_angle_from_elevation(lat, sol_decl_adj, elev); | |
let sol_offset_adj: f64 = 720.0 - 4.0 * (lon + ha_adj.to_degrees()) - eq_time_adj; | |
sol_offset_adj | |
} | |
/// Calculates the solar angular elevation (in radians) at the given location and time. | |
/// - t: Julian centuries since J2000.0 | |
/// - lat: Latitude of location | |
/// - lon: Longitude of location | |
fn solar_elevation_from_time(t: f64, lat: f64, lon: f64) -> f64 { | |
// Minutes from midnight | |
let jd: f64 = jd_from_jcent(t); | |
let offset: f64 = (jd - jd.round() - 0.5) * 1440.0; | |
let eq_time: f64 = equation_of_time(t); | |
let decl: f64 = solar_declination(t); | |
let ha: f64 = ((720.0 - offset - eq_time) / 4.0 - lon).to_radians(); | |
elevation_from_hour_angle(lat, decl, ha) | |
} | |
/// Calculates the solar angular elevation (in degrees) at the given location and time. | |
/// - date: Seconds since unix epoch | |
/// - lat: Latitude of location | |
/// - lon: Longitude of location | |
/// - Return: Solar angular elevation in degrees | |
pub fn solar_elevation(date: f64, lat: f64, lon: f64) -> f64 { | |
let jd: f64 = jd_from_epoch(date); | |
let ret: f64 = solar_elevation_from_time(jcent_from_jd(jd), lat, lon); | |
ret.to_degrees() | |
} | |
// Generates a `Map<SolarTime, f64>` which contains for all solar events the epoch (seconds) | |
/// at which they will occur, given the current date, latitude and longitude | |
/// - date: Seconds since unix epoch | |
/// - lat: Latitude of location | |
/// - lon: Longitude of location | |
pub fn solar_timetable(date: f64, lat: f64, lon: f64) -> HashMap<SolarTime, f64> { | |
let mut ret: HashMap<SolarTime, f64> = HashMap::new(); | |
let angles: HashMap<SolarTime, f64> = generate_time_angles(); | |
// Calculate Julian day | |
let jd = jd_from_epoch(date); | |
// Calculate Julian century | |
let jdn: f64 = jd.round(); | |
let t: f64 = jcent_from_jd(jdn); | |
// Calculate apparent solar noon | |
let sol_noon: f64 = time_of_solar_noon(t, lon); | |
let j_noon: f64 = jdn - 0.5 + sol_noon / 1440.0; | |
let t_noon: f64 = jcent_from_jd(j_noon); | |
// Calulate absoute time of other phenomena | |
for st in SolarTime::iterator() { | |
let angle: f64 = angles.get(&st).unwrap_or(&0.0).to_owned(); | |
let offset: f64 = time_of_solar_elevation(t, t_noon, lat, lon, angle); | |
ret.insert(st, epoch_from_jd(jdn - 0.5 + offset / 1440.0)); | |
} | |
// Insert solar noon | |
ret.insert(SolarTime::Noon, epoch_from_jd(j_noon)); | |
// Calculate solar midnight | |
ret.insert(SolarTime::Midnight, epoch_from_jd(j_noon + 0.5)); | |
ret | |
} | |
/// This function takes LAT LONG as input | |
/// and returns the sunrise and sunset local times of the place | |
pub fn get_sunrise_sunset(lat : f64, lon : f64) { | |
// Get the UNIX time | |
let unixtime = chrono::DateTime::timestamp(&chrono::offset::Utc::now()); | |
// Get the times of major events | |
let tt = solar_timetable(unixtime as f64, lat, lon); | |
// Index into the HashMap using SolarTime Enum | |
let sunrise = unix_to_normal_time(tt.get(&SolarTime::Sunset).unwrap().round() as i64); | |
let sunset = unix_to_normal_time(tt.get(&SolarTime::Sunset).unwrap().round() as i64); | |
// Return tuple of sunsrise and sunset times | |
(sunset, sunrise) | |
} | |
/// This function converts UNIX seconds to | |
/// a human readable string time | |
fn unix_to_normal_time(time : i64) -> String { | |
let naive = chrono::NaiveDateTime::from_timestamp(time, 0); | |
let datetime: chrono::DateTime<chrono::Utc> = chrono::DateTime::from_utc(naive, chrono::Utc); | |
let converted: chrono::DateTime<chrono::Local> = chrono::DateTime::from(datetime); | |
let newdate = converted.format("%H:%M:%S").to_string(); | |
// Return the time in string type | |
newdate | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment