use std::f64::consts::*;
use log::warn;
use super::hadec::HADec;
use super::lmn::LMN;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[allow(clippy::upper_case_acronyms)]
pub struct RADec {
pub ra: f64,
pub dec: f64,
}
impl RADec {
pub fn new(ra_rad: f64, dec_rad: f64) -> RADec {
Self {
ra: ra_rad,
dec: dec_rad,
}
}
pub fn new_degrees(ra_deg: f64, dec_deg: f64) -> RADec {
Self::new(ra_deg.to_radians(), dec_deg.to_radians())
}
pub fn to_hadec(self, lst_rad: f64) -> HADec {
HADec {
ha: lst_rad - self.ra,
dec: self.dec,
}
}
pub fn from_hadec(hadec: HADec, lst_rad: f64) -> Self {
Self {
ra: lst_rad - hadec.ha,
dec: hadec.dec,
}
}
pub fn weighted_average(radecs: &[Self], weights: &[f64]) -> Option<Self> {
let mut any_less_than_90 = false;
let mut any_between_90_270 = false;
let mut any_greater_than_270 = false;
for radec in radecs {
if (0.0..FRAC_PI_4).contains(&radec.ra) {
any_less_than_90 = true;
}
if (FRAC_PI_4..3.0 * FRAC_PI_4).contains(&radec.ra) {
any_between_90_270 = true;
}
if (3.0 * FRAC_PI_4..TAU).contains(&radec.ra) {
any_greater_than_270 = true;
}
}
let new_cutoff = match (any_less_than_90, any_between_90_270, any_greater_than_270) {
(false, false, false) => return None,
(true, false, false) => 0.0,
(false, true, false) => 0.0,
(false, false, true) => 0.0,
(false, true, true) => 0.0,
(true, true, false) => 0.0,
(true, false, true) => PI,
(true, true, true) => {
warn!("Attempting to find the average RADec over a collection of coordinates that span many RAs!");
0.0
}
};
let mut ra_sum = 0.0;
let mut dec_sum = 0.0;
let mut weight_sum = 0.0;
for (c, w) in radecs.iter().zip(weights.iter()) {
let ra = if c.ra > new_cutoff { c.ra - TAU } else { c.ra };
ra_sum += ra * w;
dec_sum += c.dec * w;
weight_sum += w;
}
let mut weighted_pos = Self::new(ra_sum / weight_sum, dec_sum / weight_sum);
if weighted_pos.ra < 0.0 {
weighted_pos.ra += TAU;
}
Some(weighted_pos)
}
pub fn to_lmn(&self, phase_centre: RADec) -> LMN {
let d_ra = self.ra - phase_centre.ra;
let (s_d_ra, c_d_ra) = d_ra.sin_cos();
let (s_dec, c_dec) = self.dec.sin_cos();
let (pc_s_dec, pc_c_dec) = phase_centre.dec.sin_cos();
LMN {
l: c_dec * s_d_ra,
m: s_dec * pc_c_dec - c_dec * pc_s_dec * c_d_ra,
n: s_dec * pc_s_dec + c_dec * pc_c_dec * c_d_ra,
}
}
pub fn separation(&self, b: Self) -> f64 {
unsafe { erfa_sys::eraSeps(self.ra, self.dec, b.ra, b.dec) }
}
#[cfg(feature = "mwalib")]
pub fn from_mwalib_phase_center(context: &mwalib::MetafitsContext) -> Option<RADec> {
match (
context.ra_phase_center_degrees,
context.dec_phase_center_degrees,
) {
(Some(ra), Some(dec)) => Some(RADec::new_degrees(ra, dec)),
(..) => None,
}
}
#[cfg(feature = "mwalib")]
pub fn from_mwalib_tile_pointing(context: &mwalib::MetafitsContext) -> RADec {
RADec::new_degrees(
context.ra_tile_pointing_degrees,
context.dec_tile_pointing_degrees,
)
}
#[cfg(feature = "mwalib")]
pub fn from_mwalib_phase_or_pointing(context: &mwalib::MetafitsContext) -> RADec {
match RADec::from_mwalib_phase_center(context) {
Some(radec) => radec,
None => RADec::from_mwalib_tile_pointing(context),
}
}
}
impl std::fmt::Display for RADec {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"({:.4}°, {:.4}°)",
self.ra.to_degrees(),
self.dec.to_degrees()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_to_lmn() {
let radec = RADec::new_degrees(62.0, -27.5);
let phase_centre = RADec::new_degrees(60.0, -27.0);
let lmn = radec.to_lmn(phase_centre);
let expected = LMN {
l: 0.03095623164758603,
m: -0.008971846102111436,
n: 0.9994804738961642,
};
assert_abs_diff_eq!(lmn.l, expected.l, epsilon = 1e-10);
assert_abs_diff_eq!(lmn.m, expected.m, epsilon = 1e-10);
assert_abs_diff_eq!(lmn.n, expected.n, epsilon = 1e-10);
}
#[test]
fn test_weighted_pos() {
let c1 = RADec::new_degrees(10.0, 9.0);
let w1 = 1.0;
let c2 = RADec::new_degrees(11.0, 10.0);
let w2 = 1.0;
let result = RADec::weighted_average(&[c1, c2], &[w1, w2]);
assert!(result.is_some());
let weighted_pos = result.unwrap();
assert_abs_diff_eq!(weighted_pos.ra, 10.5_f64.to_radians(), epsilon = 1e-10);
assert_abs_diff_eq!(weighted_pos.dec, 9.5_f64.to_radians(), epsilon = 1e-10);
let w1 = 3.0;
let result = RADec::weighted_average(&[c1, c2], &[w1, w2]);
assert!(result.is_some());
let weighted_pos = result.unwrap();
assert_abs_diff_eq!(weighted_pos.ra, 10.25_f64.to_radians(), epsilon = 1e-10);
assert_abs_diff_eq!(weighted_pos.dec, 9.25_f64.to_radians(), epsilon = 1e-10);
}
#[test]
fn test_weighted_pos2() {
let c1 = RADec::new_degrees(10.0, 9.0);
let w1 = 1.0;
let c2 = RADec::new_degrees(359.0, 10.0);
let w2 = 1.0;
let result = RADec::weighted_average(&[c1, c2], &[w1, w2]);
assert!(result.is_some());
let weighted_pos = result.unwrap();
assert_abs_diff_eq!(weighted_pos.ra, 4.5_f64.to_radians(), epsilon = 1e-10);
assert_abs_diff_eq!(weighted_pos.dec, 9.5_f64.to_radians(), epsilon = 1e-10);
}
#[test]
fn test_weighted_pos_single() {
let c = RADec::new(0.5, 0.75);
let w = 1.0;
let result = RADec::weighted_average(&[c], &[w]);
assert!(result.is_some());
let weighted_pos = result.unwrap();
assert_abs_diff_eq!(weighted_pos.ra, 0.5, epsilon = 1e-10);
assert_abs_diff_eq!(weighted_pos.dec, 0.75, epsilon = 1e-10);
}
#[test]
fn test_weighted_pos_empty() {
let arr: Vec<RADec> = vec![];
assert!(RADec::weighted_average(&arr, &[1.0]).is_none());
}
}