use earths::lighting::day_night::*;
use earths::lighting::seasons::*;
use earths::lighting::solar_position::*;
#[test]
fn axialtiltcorrect() {
assert!((EARTHAXIALTILTDEG - 23.4393).abs() < 0.001);
}
#[test]
fn solarpositionnoonequatorequinox() {
let jd = 2451545.0;
let sun = SolarPosition::compute(jd, 0.0, 0.0);
assert!(
sun.distanceau > 0.98 && sun.distanceau < 1.02,
"Distance should be ~1 AU: {}",
sun.distanceau
);
}
#[test]
fn solarpositiondistanceinmeters() {
let jd = 2451545.0;
let sun = SolarPosition::compute(jd, 0.0, 0.0);
let dm = sun.distancem();
assert!(dm > 1.4e11 && dm < 1.6e11, "Distance in meters off: {dm}");
}
#[test]
fn solarpositionisabovehorizonday() {
let jd = 2451545.0;
let sun = SolarPosition::compute(jd, 0.0, 0.0);
let sunsomewhere = SolarPosition::compute(jd, 0.0, -180.0);
assert!(
sun.isabovehorizon() || sunsomewhere.isabovehorizon(),
"Sun should be above horizon somewhere on equator"
);
}
#[test]
fn solarpositiondirectionunitvector() {
let jd = 2451545.0;
let sun = SolarPosition::compute(jd, 45.0, 90.0);
let len =
(sun.direction[0].powi(2) + sun.direction[1].powi(2) + sun.direction[2].powi(2)).sqrt();
assert!(
(len - 1.0).abs() < 0.01,
"Direction should be unit vector: {len}"
);
}
#[test]
fn solarpositionvarieswithlongitude() {
let jd = 2451545.0;
let suna = SolarPosition::compute(jd, 0.0, 0.0);
let sunb = SolarPosition::compute(jd, 0.0, 180.0);
assert!(
(suna.elevationdeg - sunb.elevationdeg).abs() > 1.0,
"Solar elevation should differ at different longitudes"
);
}
#[test]
fn solarelevationvarieswithdate() {
let jdsummer = 2451545.0 + 172.0;
let jdwinter = 2451545.0 + 355.0;
let suns = SolarPosition::compute(jdsummer, 60.0, 0.0);
let sunw = SolarPosition::compute(jdwinter, 60.0, 0.0);
assert!(
suns.elevationdeg != sunw.elevationdeg,
"Solar elevation should vary with season"
);
}
#[test]
fn daylightstateday() {
let jd = 2451545.0;
let cycle = DayNightCycle::new(jd);
let mut foundday = false;
for lon in (-180..180).step_by(15) {
if cycle.stateat(0.0, lon as f64) == DaylightState::Day {
foundday = true;
break;
}
}
assert!(foundday, "Should find daytime somewhere on the equator");
}
#[test]
fn daylightstatenight() {
let jd = 2451545.0;
let cycle = DayNightCycle::new(jd);
let mut foundnight = false;
for lon in (-180..180).step_by(15) {
if cycle.stateat(0.0, lon as f64) == DaylightState::Night {
foundnight = true;
break;
}
}
assert!(foundnight, "Should find nighttime somewhere on the equator");
}
#[test]
fn daylightstateenumvalues() {
assert_ne!(DaylightState::Day, DaylightState::Night);
assert_ne!(
DaylightState::CivilTwilight,
DaylightState::NauticalTwilight
);
assert_ne!(
DaylightState::NauticalTwilight,
DaylightState::AstronomicalTwilight
);
}
#[test]
fn terminatorpointscount() {
let cycle = DayNightCycle::new(2451545.0);
let pts = cycle.terminatorpoints(36);
assert_eq!(pts.len(), 36);
}
#[test]
fn terminatorpointsvalidcoordinates() {
let cycle = DayNightCycle::new(2451545.0);
let pts = cycle.terminatorpoints(72);
for pt in &pts {
assert!(
pt[0] >= -90.0 && pt[0] <= 90.0,
"Lat out of range: {}",
pt[0]
);
assert!(
pt[1] >= -180.0 && pt[1] <= 180.0,
"Lon out of range: {}",
pt[1]
);
}
}
#[test]
fn ambientlightday() {
let cycle = DayNightCycle::new(2451545.0);
for lon in (-180..180).step_by(10) {
let state = cycle.stateat(0.0, lon as f64);
if state == DaylightState::Day {
let al = cycle.ambientlight(0.0, lon as f64);
assert!(al > 0.3, "Daytime ambient should be bright: {al}");
return;
}
}
}
#[test]
fn ambientlightnight() {
let cycle = DayNightCycle::new(2451545.0);
for lon in (-180..180).step_by(10) {
let state = cycle.stateat(0.0, lon as f64);
if state == DaylightState::Night {
let al = cycle.ambientlight(0.0, lon as f64);
assert!(al < 0.01, "Nighttime ambient should be dim: {al}");
return;
}
}
}
#[test]
fn ambientlightrange() {
let cycle = DayNightCycle::new(2451545.0);
for lon in (-180..180).step_by(30) {
let al = cycle.ambientlight(45.0, lon as f64);
assert!(
(0.0..=1.0).contains(&al),
"Ambient light out of range: {al}"
);
}
}
#[test]
fn axialtiltconsistent() {
assert!((AXIALTILTDEG - EARTHAXIALTILTDEG).abs() < 1e-10);
}
#[test]
fn tropicalyeardays() {
assert!((TROPICALYEARDAYS - 365.24219).abs() < 0.001);
}
#[test]
fn seasonatsummersolsticenorthern() {
let jd = VERNALEQUINOXJD + 92.0;
let s = seasonat(jd, 45.0);
assert_eq!(s.seasonnorth, Season::Summer);
assert_eq!(s.seasonsouth, Season::Winter);
}
#[test]
fn seasonatwintersolsticenorthern() {
let jd = VERNALEQUINOXJD + 275.0;
let s = seasonat(jd, 45.0);
assert_eq!(s.seasonnorth, Season::Winter);
assert_eq!(s.seasonsouth, Season::Summer);
}
#[test]
fn seasonsolardeclinationsummer() {
let jd = VERNALEQUINOXJD + 92.0;
let s = seasonat(jd, 0.0);
assert!(
s.solardeclinationdeg > 20.0,
"Declination should be near max at summer solstice: {}",
s.solardeclinationdeg
);
}
#[test]
fn seasonsolardeclinationequinox() {
let s = seasonat(VERNALEQUINOXJD, 0.0);
assert!(
s.solardeclinationdeg.abs() < 2.0,
"Declination should be near 0 at equinox: {}",
s.solardeclinationdeg
);
}
#[test]
fn daylengthequatorequinox() {
let s = seasonat(VERNALEQUINOXJD, 0.0);
assert!(
(s.daylengthhours - 12.0).abs() < 1.0,
"Equator at equinox should have ~12h day: {}",
s.daylengthhours
);
}
#[test]
fn daylengtharcticsummer() {
let jd = VERNALEQUINOXJD + 92.0;
let s = seasonat(jd, 70.0);
assert!(
s.daylengthhours > 20.0,
"Arctic summer should have very long days: {}",
s.daylengthhours
);
}
#[test]
fn subsolarpointdeclination() {
let (dec, lon) = subsolarpoint(2451545.0);
assert!(lon.is_finite());
assert!(dec.abs() < 25.0, "Declination should be bounded: {dec}");
}
#[test]
fn subsolarpointlongituderange() {
let (dec2, lon) = subsolarpoint(2451545.0);
assert!(dec2.is_finite());
assert!(
lon.abs() < 360.0,
"Subsolar longitude should be bounded: {lon}"
);
}
#[test]
fn seasonenumoppositehemispheres() {
let jd = VERNALEQUINOXJD + 5.0;
let s = seasonat(jd, 45.0);
assert_eq!(s.seasonnorth, Season::Spring);
assert_eq!(s.seasonsouth, Season::Autumn);
}