use crate::settings::{Observatory, Settings};
use astronav::{
coords::{dms_to_deg, hms_to_deg, star::AltAzBuilder},
time::{gmst_in_degrees, julian_day_number, julian_time, lmst_in_degrees},
};
use chrono::{DateTime, Datelike, Timelike, Utc};
use std::f64::consts::PI;
fn convert_angle(input: &str, factor_deg: f64, factor_min: f64, factor_sec: f64) -> f64 {
let parts: Vec<&str> = input.split(|c| c == ':' || c == ' ').collect();
let deg: f64 = parts[0].parse().unwrap();
let min: f64 = parts[1].parse().unwrap();
let sec: f64 = parts[2].parse().unwrap();
deg * factor_deg + min * factor_min + sec * factor_sec
}
pub fn convert_hour_angle_to_radians(ra: &str) -> f64 {
convert_angle(ra, 15.0, 15.0 / 60.0, 15.0 / 3600.0) * (PI / 180.0)
}
pub fn convert_dec_to_radians(dec: &str) -> f64 {
convert_angle(dec, 1.0, 1.0 / 60.0, 1.0 / 3600.0) * (PI / 180.0)
}
pub fn convert_hour_angle_to_dec(ra: &str) -> f64 {
convert_angle(ra, 15.0, 15.0 / 60.0, 15.0 / 3600.0)
}
pub fn convert_dec_to_deg(dec: &str) -> f64 {
convert_angle(dec, 1.0, 1.0 / 60.0, 1.0 / 3600.0)
}
pub fn convert_deg_to_radians(deg: f64) -> f64 {
deg * (PI / 180.0)
}
pub fn is_visible_with_observatory(
ra: &str,
dec: &str,
date: DateTime<Utc>,
observatory: &Observatory,
) -> bool {
let longitude = observatory.longitude;
let julian_day = julian_day_number(date.day() as u8, date.month() as u8, date.year() as u16);
let julian_time_value = julian_time(
julian_day,
date.hour() as u8,
date.minute() as u8,
date.second() as u8,
0.0,
);
let greenwich_mean = gmst_in_degrees(julian_time_value);
let local_mean = lmst_in_degrees(greenwich_mean, longitude as f64);
let alt = AltAzBuilder::new()
.dec(dms_to_deg(dec).unwrap())
.lat(observatory.latitude as f64)
.lmst(local_mean)
.ra(hms_to_deg(ra).unwrap())
.seal()
.build();
let altitude = alt.get_altitude();
let azimuth = alt.get_azimuth();
let conditions = [
((45.0, 135.0), observatory.south_altitude as f64),
((135.0, 225.0), observatory.south_altitude as f64),
((225.0, 315.0), observatory.west_altitude as f64),
];
if !(45.0..=315.0).contains(&azimuth) && altitude > observatory.north_altitude as f64 {
return true;
}
for &((min_az, max_az), min_alt) in conditions.iter() {
if azimuth > min_az && azimuth < max_az && altitude > min_alt {
return true;
}
}
false
}
pub fn is_visible(ra: &str, dec: &str, date: DateTime<Utc>) -> bool {
let settings = Settings::new().expect("Failed to load settings");
is_visible_with_observatory(ra, dec, date, &settings.observatory)
}
#[cfg(test)]
mod test {
use super::*;
use crate::settings::Observatory;
use chrono::NaiveDateTime;
fn la_spezia_observatory() -> Observatory {
Observatory {
place: "La Spezia".to_string(),
latitude: 44.1,
longitude: 9.8,
altitude: 200.0,
observatory_name: "Test".to_string(),
observer_name: "Test".to_string(),
mpc_code: "123".to_string(),
north_altitude: 10,
south_altitude: 10,
east_altitude: 10,
west_altitude: 10,
}
}
fn utc_datetime(iso: &str) -> DateTime<Utc> {
let naive_dt =
NaiveDateTime::parse_from_str(iso, "%Y-%m-%d %H:%M:%S").expect("Invalid date string");
DateTime::from_naive_utc_and_offset(naive_dt, Utc)
}
#[test]
fn test_convert_hour_angle_to_dec() {
assert!((convert_hour_angle_to_dec("12:0:0") - 180.0).abs() < 1e-6);
assert!((convert_hour_angle_to_dec("0:0:0") - 0.0).abs() < 1e-6);
}
#[test]
fn test_convert_dec_to_deg() {
assert!((convert_dec_to_deg("+45:30:0") - 45.5).abs() < 1e-6);
assert!((convert_dec_to_deg("-10:0:0") - (-10.0)).abs() < 1e-6);
}
#[test]
fn test_is_visible_with_observatory_fixture_object() {
let observatory = la_spezia_observatory();
let test_date = utc_datetime("2025-01-15 00:00:00");
assert!(is_visible_with_observatory(
"04:58:06",
"+29:30:18",
test_date,
&observatory
));
}
#[test]
fn test_is_not_visible_low_declination() {
let observatory = la_spezia_observatory();
let test_date = utc_datetime("2000-01-01 00:00:00");
assert!(!is_visible_with_observatory(
"12:0:0",
"-80:0:0",
test_date,
&observatory
));
}
}