use crate::*;
use crate::transforms::{ra_dec_to_alt_az_erfa, alt_az_to_ra_dec};
use chrono::{TimeZone, Utc};
const EPSILON: f64 = 0.1;
#[test]
fn test_ra_dec_to_alt_az_astropy_crosscheck() {
let observer = Location {
latitude_deg: 31.9583,
longitude_deg: -111.6,
altitude_m: 2120.0,
};
let dt = Utc.with_ymd_and_hms(2024, 8, 4, 6, 0, 0).unwrap();
let ra = 279.23473479;
let dec = 38.78368896;
let (alt, az) = transforms::ra_dec_to_alt_az(ra, dec, dt, &observer).unwrap();
println!("Alt: {}", alt);
println!("AZ: {}", az);
assert!(
(alt - 77.775).abs() < EPSILON,
"Alt = {}, expected ≈ 77.775",
alt
);
assert!(
(az - 307.386).abs() < EPSILON,
"Az = {}, expected ≈ 307.386",
az
);
}
#[test]
fn test_ra_dec_to_alt_az_negative_azimuth_wrap() {
let dt = Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).unwrap();
let loc = Location {
latitude_deg: 0.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let ra = 180.0;
let dec = -10.0;
let (_alt, az) = transforms::ra_dec_to_alt_az(ra, dec, dt, &loc).unwrap();
assert!((0.0..=360.0).contains(&az), "Azimuth should be normalized to [0, 360), got {}", az);
}
#[test]
fn test_ra_dec_to_alt_az_zenith_edge_case() {
let observer = Location {
latitude_deg: 45.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 3, 20, 12, 0, 0).unwrap();
let ra = 0.0; let dec = 45.0;
let (alt, az) = transforms::ra_dec_to_alt_az(ra, dec, dt, &observer).unwrap();
if alt > 89.9 {
assert!(az == 0.0 || az == 180.0,
"At zenith, azimuth should be 0 or 180, got {}", az);
}
}
#[test]
fn test_ra_dec_to_alt_az_polar_observer() {
let observer = Location {
latitude_deg: 89.9, longitude_deg: 0.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 6, 21, 0, 0, 0).unwrap();
let ra = 37.95456;
let dec = 89.26411;
let (alt, az) = transforms::ra_dec_to_alt_az(ra, dec, dt, &observer).unwrap();
assert!((-90.0..=90.0).contains(&alt), "Altitude out of range: {}", alt);
assert!((0.0..=360.0).contains(&az), "Azimuth out of range: {}", az);
assert!(alt > 88.0, "Polaris should be near zenith from latitude 89.9°, got alt={}°", alt);
assert!(!az.is_nan(), "Azimuth should not be NaN even near pole");
}
#[test]
fn test_azimuth_negative_normalization() {
let observer = Location {
latitude_deg: -45.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).unwrap();
let (alt, az) = transforms::ra_dec_to_alt_az(270.0, -30.0, dt, &observer).unwrap();
assert!((-90.0..=90.0).contains(&alt), "Altitude should be valid, got {}", alt);
assert!((0.0..360.0).contains(&az), "Azimuth should be [0,360), got {}", az);
assert!(alt > -90.0, "Object should be above theoretical horizon");
}
#[test]
fn test_ra_dec_to_alt_az_numerical_stability() {
let observer = Location {
latitude_deg: 0.0, longitude_deg: 0.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 3, 20, 6, 0, 0).unwrap();
let lst = observer.local_sidereal_time(dt);
let ra = (lst + 6.0) * 15.0; let dec = 0.0;
let (alt, az) = transforms::ra_dec_to_alt_az(ra, dec, dt, &observer).unwrap();
assert!((-90.0..=90.0).contains(&alt), "Altitude out of range: {}", alt);
assert!((0.0..=360.0).contains(&az), "Azimuth out of range: {}", az);
assert!(alt.abs() < 1.0, "Object on horizon should have alt ≈ 0°, got {}°", alt);
assert!((0.0..360.0).contains(&az), "Azimuth should be valid");
}
#[test]
fn test_transforms_edge_cases() {
let dt = Utc.with_ymd_and_hms(2024, 8, 4, 12, 0, 0).unwrap();
let loc_np = Location {
latitude_deg: 90.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let (alt1, _az1) = transforms::ra_dec_to_alt_az(0.0, 45.0, dt, &loc_np).unwrap();
assert!((alt1 - 45.0).abs() < 1e-10);
let loc_sp = Location {
latitude_deg: -90.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let (alt2, _az2) = transforms::ra_dec_to_alt_az(0.0, -45.0, dt, &loc_sp).unwrap();
assert!((alt2 - 45.0).abs() < 1e-10);
let loc = Location {
latitude_deg: 23.5,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let lst = loc.local_sidereal_time(dt);
let (alt3, _az3) = transforms::ra_dec_to_alt_az(lst * 15.0, 23.5, dt, &loc).unwrap();
assert!((alt3 - 90.0).abs() < 0.001);
}
#[test]
fn test_transforms_azimuth_branches() {
let dt = Utc.with_ymd_and_hms(2024, 8, 4, 12, 0, 0).unwrap();
let location = Location {
latitude_deg: 0.0, longitude_deg: 0.0,
altitude_m: 0.0,
};
let lst = location.local_sidereal_time(dt);
let ra_meridian = lst * 15.0;
let (_alt_m, az_m) = transforms::ra_dec_to_alt_az(ra_meridian, 45.0, dt, &location).unwrap();
assert!(az_m < 5.0 || az_m > 175.0 && az_m < 185.0,
"Object on meridian should have az near 0° or 180°, got {}°", az_m);
let dec = 30.0;
let ra_east = (lst - 3.0) * 15.0;
let (_, az_east) = transforms::ra_dec_to_alt_az(ra_east, dec, dt, &location).unwrap();
let ra_west = (lst + 3.0) * 15.0;
let (_, az_west) = transforms::ra_dec_to_alt_az(ra_west, dec, dt, &location).unwrap();
assert!(az_east != az_west, "Objects at different hour angles should have different azimuths");
assert!((0.0..360.0).contains(&az_east), "East azimuth should be valid, got {}°", az_east);
assert!((0.0..360.0).contains(&az_west), "West azimuth should be valid, got {}°", az_west);
}
#[test]
fn test_transforms_negative_azimuth_normalization() {
let observer = Location {
latitude_deg: 45.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
let lst = observer.local_sidereal_time(dt);
let ra_hours = lst - 9.0; let ra = if ra_hours < 0.0 { (ra_hours + 24.0) * 15.0 } else { ra_hours * 15.0 };
let dec = 60.0;
let (_, az) = ra_dec_to_alt_az(ra, dec, dt, &observer).unwrap();
assert!((0.0..360.0).contains(&az), "Azimuth should be normalized from negative");
}
#[test]
fn test_ra_dec_to_alt_az_erfa_basic() {
let observer = Location {
latitude_deg: 40.0,
longitude_deg: -74.0,
altitude_m: 100.0,
};
let dt = Utc.with_ymd_and_hms(2024, 6, 21, 0, 0, 0).unwrap();
let ra = 279.23473479;
let dec = 38.78368896;
let (alt, az) = ra_dec_to_alt_az_erfa(
ra, dec, dt, &observer,
Some(1013.25), Some(20.0), Some(0.5)
).unwrap();
assert!((-90.0..=90.0).contains(&alt), "Altitude out of range: {}", alt);
assert!((0.0..360.0).contains(&az), "Azimuth out of range: {}", az);
}
#[test]
fn test_ra_dec_to_alt_az_erfa_no_atmosphere() {
let observer = Location {
latitude_deg: 0.0,
longitude_deg: 0.0,
altitude_m: 600000.0, };
let dt = Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).unwrap();
let ra = 83.633;
let dec = 22.0145;
let (alt_refr, az_refr) = ra_dec_to_alt_az_erfa(
ra, dec, dt, &observer,
Some(1013.25), Some(15.0), Some(0.5)
).unwrap();
let (alt_no_refr, az_no_refr) = ra_dec_to_alt_az_erfa(
ra, dec, dt, &observer,
Some(0.0), Some(0.0), Some(0.0)
).unwrap();
assert!((az_refr - az_no_refr).abs() < 0.001,
"Azimuth should not change with refraction");
let refr_diff = (alt_refr - alt_no_refr).abs();
assert!(refr_diff > 0.0 && refr_diff < 1.0,
"Refraction effect should be small but non-zero: {} deg", refr_diff);
}
#[test]
fn test_ra_dec_to_alt_az_erfa_default_atmosphere() {
let observer = Location {
latitude_deg: 51.4779,
longitude_deg: -0.0015,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 3, 20, 12, 0, 0).unwrap();
let ra = 0.0;
let dec = 0.0;
let (alt, az) = ra_dec_to_alt_az_erfa(
ra, dec, dt, &observer,
None, None, None
).unwrap();
assert!((-90.0..=90.0).contains(&alt));
assert!((0.0..360.0).contains(&az));
}
#[test]
fn test_ra_dec_to_alt_az_erfa_extreme_conditions() {
let observer = Location {
latitude_deg: -69.0, longitude_deg: 39.0,
altitude_m: 3000.0,
};
let dt = Utc.with_ymd_and_hms(2024, 7, 1, 0, 0, 0).unwrap();
let ra = 187.0;
let dec = -63.0;
let (alt, az) = ra_dec_to_alt_az_erfa(
ra, dec, dt, &observer,
Some(680.0), Some(-40.0), Some(0.1)
).unwrap();
assert!((-90.0..=90.0).contains(&alt));
assert!((0.0..360.0).contains(&az));
}
#[test]
fn test_ra_dec_to_alt_az_erfa_vs_original() {
let observer = Location {
latitude_deg: 33.356,
longitude_deg: -116.863,
altitude_m: 1706.0,
};
let dt = Utc.with_ymd_and_hms(2024, 9, 15, 3, 30, 0).unwrap();
let ra = 88.7929;
let dec = 7.4061;
let (alt_orig, az_orig) = ra_dec_to_alt_az(ra, dec, dt, &observer).unwrap();
let (alt_erfa, az_erfa) = ra_dec_to_alt_az_erfa(
ra, dec, dt, &observer,
Some(0.0), Some(0.0), Some(0.0) ).unwrap();
let alt_diff = (alt_orig - alt_erfa).abs();
let az_diff = (az_orig - az_erfa).abs();
assert!(alt_diff < 1.0, "Altitude difference too large: {} deg", alt_diff);
assert!(az_diff < 1.0, "Azimuth difference too large: {} deg", az_diff);
}
#[test]
fn test_ra_dec_to_alt_az_erfa_high_altitude() {
let observer = Location {
latitude_deg: 19.8207, longitude_deg: -155.4681,
altitude_m: 4205.0,
};
let dt = Utc.with_ymd_and_hms(2024, 12, 21, 6, 0, 0).unwrap();
let ra = 95.988;
let dec = -52.696;
let (alt, az) = ra_dec_to_alt_az_erfa(
ra, dec, dt, &observer,
Some(615.0), Some(2.0), Some(0.2) ).unwrap();
assert!((-90.0..=90.0).contains(&alt));
assert!((0.0..360.0).contains(&az));
}
#[test]
fn test_ra_dec_to_alt_az_erfa_pole_star() {
let ra = 37.95456;
let dec = 89.26411;
let dt = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
let observer_np = Location {
latitude_deg: 90.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let (alt_np, _) = ra_dec_to_alt_az_erfa(
ra, dec, dt, &observer_np,
Some(1013.25), Some(-30.0), Some(0.1)
).unwrap();
assert!((alt_np - dec).abs() < 0.2,
"Polaris altitude from North Pole should be ~89.26°, got {}°", alt_np);
let observer_mid = Location {
latitude_deg: 45.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let (alt_mid, az_mid) = ra_dec_to_alt_az_erfa(
ra, dec, dt, &observer_mid,
Some(1013.25), Some(10.0), Some(0.5)
).unwrap();
assert!((alt_mid - 45.0).abs() < 1.0,
"Polaris altitude from 45°N should be ~45°, got {}°", alt_mid);
assert!(!(5.0..=355.0).contains(&az_mid),
"Polaris azimuth should be near North, got {}°", az_mid);
}
#[test]
fn test_ra_dec_to_alt_az_erfa_horizon() {
let observer = Location {
latitude_deg: 50.0,
longitude_deg: 10.0,
altitude_m: 200.0,
};
let dt = Utc.with_ymd_and_hms(2024, 6, 21, 20, 0, 0).unwrap();
let lst = observer.local_sidereal_time(dt);
let ra = (lst + 6.0) * 15.0; let dec = 40.0;
let (alt_with_refr, _) = ra_dec_to_alt_az_erfa(
ra, dec, dt, &observer,
Some(1013.25), Some(15.0), Some(0.5)
).unwrap();
let (alt_no_refr, _) = ra_dec_to_alt_az_erfa(
ra, dec, dt, &observer,
Some(0.0), Some(0.0), Some(0.0)
).unwrap();
if alt_no_refr.abs() < 10.0 {
let refr = alt_with_refr - alt_no_refr;
assert!(refr > 0.01, "Refraction near horizon should be positive and significant");
}
}
#[test]
fn test_alt_az_to_ra_dec_basic() {
let observer = Location {
latitude_deg: 40.0,
longitude_deg: -74.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 6, 21, 12, 0, 0).unwrap();
let lst = observer.local_sidereal_time(dt);
let expected_ra = (lst * 15.0) % 360.0;
let expected_dec = 40.0;
let (ra, dec) = alt_az_to_ra_dec(90.0, 0.0, dt, &observer).unwrap();
assert!((dec - expected_dec).abs() < 0.001,
"Zenith declination should equal latitude: got {}, expected {}", dec, expected_dec);
let ra_diff = (ra - expected_ra).abs();
let ra_diff_wrapped = (ra_diff).min(360.0 - ra_diff);
assert!(ra_diff_wrapped < 5.0,
"Zenith RA should be close to LST: got {}, expected {}", ra, expected_ra);
}
#[test]
fn test_alt_az_to_ra_dec_round_trip() {
let observer = Location {
latitude_deg: 45.0,
longitude_deg: -75.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 8, 15, 20, 30, 0).unwrap();
let test_objects = [
(279.23473479, 38.78368896), (83.633, 22.0145), (201.298, -11.171), (310.358, 45.280), (0.0, 0.0), (180.0, -45.0), ];
for (original_ra, original_dec) in test_objects {
let (alt, az) = ra_dec_to_alt_az(original_ra, original_dec, dt, &observer).unwrap();
if alt < -5.0 {
continue;
}
let (recovered_ra, recovered_dec) = alt_az_to_ra_dec(alt, az, dt, &observer).unwrap();
let ra_error = (recovered_ra - original_ra).abs().min(360.0 - (recovered_ra - original_ra).abs());
let dec_error = (recovered_dec - original_dec).abs();
assert!(ra_error < 0.001,
"RA round-trip error too large: {} -> {} -> {} (error: {:.6}°)",
original_ra, recovered_ra, original_ra, ra_error);
assert!(dec_error < 0.001,
"Dec round-trip error too large: {} -> {} -> {} (error: {:.6}°)",
original_dec, recovered_dec, original_dec, dec_error);
}
}
#[test]
fn test_alt_az_to_ra_dec_edge_cases() {
let observer = Location {
latitude_deg: 30.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 3, 20, 0, 0, 0).unwrap();
let (ra1, dec1) = alt_az_to_ra_dec(0.0, 0.0, dt, &observer).unwrap(); let (ra2, dec2) = alt_az_to_ra_dec(0.0, 90.0, dt, &observer).unwrap(); let (ra3, dec3) = alt_az_to_ra_dec(0.0, 180.0, dt, &observer).unwrap(); let (ra4, dec4) = alt_az_to_ra_dec(0.0, 270.0, dt, &observer).unwrap();
assert!((0.0..360.0).contains(&ra1), "North horizon RA should be valid");
assert!((-90.0..=90.0).contains(&dec1), "North horizon Dec should be valid");
assert!((0.0..360.0).contains(&ra2), "East horizon RA should be valid");
assert!((-90.0..=90.0).contains(&dec2), "East horizon Dec should be valid");
assert!((0.0..360.0).contains(&ra3), "South horizon RA should be valid");
assert!((-90.0..=90.0).contains(&dec3), "South horizon Dec should be valid");
assert!((0.0..360.0).contains(&ra4), "West horizon RA should be valid");
assert!((-90.0..=90.0).contains(&dec4), "West horizon Dec should be valid");
let (ra_nadir, dec_nadir) = alt_az_to_ra_dec(-90.0, 0.0, dt, &observer).unwrap();
assert!((0.0..360.0).contains(&ra_nadir), "Nadir RA should be valid");
assert!((-90.0..=90.0).contains(&dec_nadir), "Nadir Dec should be valid");
}
#[test]
fn test_alt_az_to_ra_dec_polar_regions() {
let observer_north = Location {
latitude_deg: 89.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let observer_south = Location {
latitude_deg: -89.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 6, 21, 12, 0, 0).unwrap();
let (ra_n, dec_n) = alt_az_to_ra_dec(80.0, 45.0, dt, &observer_north).unwrap();
let (ra_s, dec_s) = alt_az_to_ra_dec(80.0, 45.0, dt, &observer_south).unwrap();
assert!((0.0..360.0).contains(&ra_n), "Polar north RA should be valid");
assert!((-90.0..=90.0).contains(&dec_n), "Polar north Dec should be valid");
assert!((0.0..360.0).contains(&ra_s), "Polar south RA should be valid");
assert!((-90.0..=90.0).contains(&dec_s), "Polar south Dec should be valid");
assert!(dec_n > 70.0, "High object from North Pole should have high Dec");
assert!(dec_s < -70.0, "High object from South Pole should have low Dec");
}
#[test]
fn test_alt_az_to_ra_dec_input_validation() {
let observer = Location {
latitude_deg: 0.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
assert!(alt_az_to_ra_dec(-90.1, 0.0, dt, &observer).is_err());
assert!(alt_az_to_ra_dec(90.1, 0.0, dt, &observer).is_err());
assert!(alt_az_to_ra_dec(0.0, -0.1, dt, &observer).is_err());
assert!(alt_az_to_ra_dec(0.0, 360.0, dt, &observer).is_err());
assert!(alt_az_to_ra_dec(0.0, 360.1, dt, &observer).is_err());
assert!(alt_az_to_ra_dec(-90.0, 0.0, dt, &observer).is_ok());
assert!(alt_az_to_ra_dec(90.0, 0.0, dt, &observer).is_ok());
assert!(alt_az_to_ra_dec(0.0, 0.0, dt, &observer).is_ok());
assert!(alt_az_to_ra_dec(0.0, 359.9, dt, &observer).is_ok());
}
#[test]
fn test_alt_az_to_ra_dec_quadrant_handling() {
let observer = Location {
latitude_deg: 40.0,
longitude_deg: -74.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 9, 22, 18, 0, 0).unwrap();
let quadrants = [
(45.0, 45.0), (45.0, 135.0), (45.0, 225.0), (45.0, 315.0), ];
for (alt, az) in quadrants {
let (ra, dec) = alt_az_to_ra_dec(alt, az, dt, &observer).unwrap();
assert!((0.0..360.0).contains(&ra), "RA should be valid for az={}: got {}", az, ra);
assert!((-90.0..=90.0).contains(&dec), "Dec should be valid for az={}: got {}", az, dec);
let (alt_recovered, az_recovered) = ra_dec_to_alt_az(ra, dec, dt, &observer).unwrap();
let alt_error = (alt_recovered - alt).abs();
let az_error = (az_recovered - az).abs().min(360.0 - (az_recovered - az).abs());
assert!(alt_error < 0.001, "Alt round-trip error for az={}: {:.6}°", az, alt_error);
assert!(az_error < 0.001, "Az round-trip error for az={}: {:.6}°", az, az_error);
}
}
#[test]
fn test_alt_az_to_ra_dec_equatorial_observer() {
let observer = Location {
latitude_deg: 0.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 3, 20, 12, 0, 0).unwrap();
let (_ra_zenith, dec_zenith) = alt_az_to_ra_dec(90.0, 0.0, dt, &observer).unwrap();
assert!(dec_zenith.abs() < 0.1, "Zenith object from equator should have Dec ≈ 0, got {}", dec_zenith);
let (_, dec_north) = alt_az_to_ra_dec(0.0, 0.0, dt, &observer).unwrap(); let (_, dec_south) = alt_az_to_ra_dec(0.0, 180.0, dt, &observer).unwrap();
assert!(dec_north > 89.0, "North horizon from equator should have Dec ≈ 90°, got {}", dec_north);
assert!(dec_south < -89.0, "South horizon from equator should have Dec ≈ -90°, got {}", dec_south);
}
#[test]
fn test_alt_az_to_ra_dec_circumpolar_objects() {
let observer = Location {
latitude_deg: 60.0, longitude_deg: 0.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
let (ra1, dec1) = alt_az_to_ra_dec(60.0, 0.0, dt, &observer).unwrap(); let (ra2, dec2) = alt_az_to_ra_dec(30.0, 90.0, dt, &observer).unwrap(); let (ra3, dec3) = alt_az_to_ra_dec(30.0, 270.0, dt, &observer).unwrap();
assert!((0.0..360.0).contains(&ra1) && (-90.0..=90.0).contains(&dec1));
assert!((0.0..360.0).contains(&ra2) && (-90.0..=90.0).contains(&dec2));
assert!((0.0..360.0).contains(&ra3) && (-90.0..=90.0).contains(&dec3));
assert!(dec1 > 50.0, "High northern object should have high Dec, got {}", dec1);
let (alt1_rt, az1_rt) = ra_dec_to_alt_az(ra1, dec1, dt, &observer).unwrap();
let (alt2_rt, az2_rt) = ra_dec_to_alt_az(ra2, dec2, dt, &observer).unwrap();
let (alt3_rt, az3_rt) = ra_dec_to_alt_az(ra3, dec3, dt, &observer).unwrap();
assert!((alt1_rt - 60.0).abs() < 0.001 && (az1_rt - 0.0).abs() < 0.001);
assert!((alt2_rt - 30.0).abs() < 0.001 && (az2_rt - 90.0).abs() < 0.001);
assert!((alt3_rt - 30.0).abs() < 0.001 && (az3_rt - 270.0).abs() < 0.001);
}
#[test]
fn test_alt_az_to_ra_dec_numerical_stability() {
let observer = Location {
latitude_deg: 45.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let dt = Utc.with_ymd_and_hms(2024, 6, 21, 12, 0, 0).unwrap();
let small_altitudes = [-89.99, -45.0, -0.01, 0.0, 0.01, 45.0, 89.99];
for &alt in &small_altitudes {
for az in [0.0, 90.0, 180.0, 270.0] {
let result = alt_az_to_ra_dec(alt, az, dt, &observer);
assert!(result.is_ok(), "Should not fail for alt={}, az={}", alt, az);
let (ra, dec) = result.unwrap();
assert!(ra.is_finite() && dec.is_finite(),
"Should return finite values for alt={}, az={}: ra={}, dec={}", alt, az, ra, dec);
assert!((0.0..360.0).contains(&ra), "RA should be valid");
assert!((-90.0..=90.0).contains(&dec), "Dec should be valid");
}
}
}
#[test]
fn test_alt_az_to_ra_dec_vs_known_stars() {
let observer = Location {
latitude_deg: 34.0522, longitude_deg: -118.2437,
altitude_m: 100.0,
};
let dt = Utc.with_ymd_and_hms(2024, 7, 4, 8, 0, 0).unwrap();
let vega_ra = 279.23473479;
let vega_dec = 38.78368896;
let (vega_alt, vega_az) = ra_dec_to_alt_az(vega_ra, vega_dec, dt, &observer).unwrap();
if vega_alt > 0.0 {
let (recovered_ra, recovered_dec) = alt_az_to_ra_dec(vega_alt, vega_az, dt, &observer).unwrap();
let ra_error = (recovered_ra - vega_ra).abs().min(360.0 - (recovered_ra - vega_ra).abs());
let dec_error = (recovered_dec - vega_dec).abs();
assert!(ra_error < 0.0001,
"Vega RA error too large: {:.6}° (recovered {}, original {})",
ra_error, recovered_ra, vega_ra);
assert!(dec_error < 0.0001,
"Vega Dec error too large: {:.6}° (recovered {}, original {})",
dec_error, recovered_dec, vega_dec);
}
}