use core::{f64::consts::FRAC_PI_2, fmt};
use super::Spherical;
use crate::{Angle, Coordinate, Position, error::Result, unit};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Horizontal(Spherical);
impl Horizontal {
pub fn new(azimuth: Angle, elevation: Angle) -> Self {
Horizontal(Spherical::new(azimuth, elevation))
}
pub fn from_radians(azimuth: f64, elevation: f64) -> Result<Self> {
Ok(Horizontal(Spherical::from_radians(azimuth, elevation)?))
}
pub fn from_degrees(azimuth: f64, elevation: f64) -> Result<Self> {
Ok(Horizontal(Spherical::from_degrees(azimuth, elevation)?))
}
pub fn azimuth(self) -> Angle {
self.0.longitude()
}
pub fn elevation(self) -> Angle {
self.0.latitude()
}
pub fn zenith_angle(self) -> Angle {
Angle::from_radians(FRAC_PI_2 - self.elevation().rad())
.expect("FRAC_PI_2 minus a finite angle is finite")
}
pub fn as_spherical(self) -> Spherical {
self.0
}
pub fn distance_to(self, other: Horizontal) -> Angle {
self.0.distance_to(other.0)
}
pub fn xyz(self, distance: Coordinate) -> Position {
self.0.xyz(distance)
}
}
impl fmt::Display for Horizontal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let decimals = f.precision().unwrap_or(3);
write!(
f,
"az={:.decimals$}° el={:.decimals$}°",
self.azimuth().deg(),
self.elevation().deg()
)
}
}
impl approx::AbsDiffEq for Horizontal {
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
unit::UAS
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.0.abs_diff_eq(&other.0, epsilon)
}
}
#[cfg(test)]
mod tests {
use approx::assert_abs_diff_eq;
use super::*;
#[test]
fn round_trip_degrees() {
let h = Horizontal::from_degrees(180.0, 45.0).unwrap();
assert!((h.azimuth().deg() - 180.0).abs() < 1e-12);
assert!((h.elevation().deg() - 45.0).abs() < 1e-12);
}
#[test]
fn zenith_angle_relates_to_elevation() {
let zenith = Horizontal::from_degrees(0.0, 90.0).unwrap();
assert!(zenith.zenith_angle().deg().abs() < 1e-12);
let horizon = Horizontal::from_degrees(0.0, 0.0).unwrap();
assert!((horizon.zenith_angle().deg() - 90.0).abs() < 1e-12);
}
#[test]
fn approx_eq_across_azimuth_wrap() {
let a = Horizontal::from_degrees(359.9999, 30.0).unwrap();
let b = Horizontal::from_degrees(-0.0001, 30.0).unwrap();
assert_abs_diff_eq!(a, b, epsilon = unit::ARCSEC);
}
}