use crate::math::normalize_degrees_0_to_360;
use core::fmt;
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, Clone, PartialEq)]
pub enum Error {
InvalidLatitude {
value: f64,
},
InvalidLongitude {
value: f64,
},
InvalidElevationAngle {
value: f64,
},
InvalidPressure {
value: f64,
},
InvalidTemperature {
value: f64,
},
InvalidDateTime {
message: &'static str,
},
ComputationError {
message: &'static str,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLatitude { value } => {
write!(
f,
"invalid latitude {value}° (must be between -90° and +90°)"
)
}
Self::InvalidLongitude { value } => {
write!(
f,
"invalid longitude {value}° (must be between -180° and +180°)"
)
}
Self::InvalidElevationAngle { value } => {
write!(
f,
"invalid elevation angle {value}° (must be between -90° and +90°)"
)
}
Self::InvalidPressure { value } => {
write!(f, "invalid pressure {value} mbar (must be positive)")
}
Self::InvalidTemperature { value } => {
write!(
f,
"invalid temperature {value}°C (must be above absolute zero)"
)
}
Self::InvalidDateTime { message } => {
write!(f, "invalid date/time: {message}")
}
Self::ComputationError { message } => {
write!(f, "computation error: {message}")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
pub fn check_latitude(latitude: f64) -> Result<()> {
if !(-90.0..=90.0).contains(&latitude) {
return Err(Error::InvalidLatitude { value: latitude });
}
Ok(())
}
pub fn check_longitude(longitude: f64) -> Result<()> {
if !(-180.0..=180.0).contains(&longitude) {
return Err(Error::InvalidLongitude { value: longitude });
}
Ok(())
}
pub fn check_coordinates(latitude: f64, longitude: f64) -> Result<()> {
check_latitude(latitude)?;
check_longitude(longitude)
}
pub fn check_elevation_angle(elevation_angle: f64) -> Result<()> {
if !elevation_angle.is_finite() || !(-90.0..=90.0).contains(&elevation_angle) {
return Err(Error::InvalidElevationAngle {
value: elevation_angle,
});
}
Ok(())
}
pub fn check_pressure(pressure: f64) -> Result<()> {
if !pressure.is_finite() || pressure <= 0.0 || pressure > 2000.0 {
return Err(Error::InvalidPressure { value: pressure });
}
Ok(())
}
pub fn check_temperature(temperature: f64) -> Result<()> {
if !(-273.15..=100.0).contains(&temperature) {
return Err(Error::InvalidTemperature { value: temperature });
}
Ok(())
}
pub fn check_azimuth(azimuth: f64) -> Result<f64> {
if !azimuth.is_finite() {
return Err(Error::ComputationError {
message: "azimuth is not finite",
});
}
Ok(normalize_degrees_0_to_360(azimuth))
}
pub fn check_zenith_angle(zenith: f64) -> Result<f64> {
if !zenith.is_finite() {
return Err(Error::ComputationError {
message: "zenith angle is not finite",
});
}
if !(0.0..=180.0).contains(&zenith) {
return Err(Error::ComputationError {
message: "zenith angle must be between 0° and 180°",
});
}
Ok(zenith)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_latitude_validation() {
assert!(check_latitude(0.0).is_ok());
assert!(check_latitude(90.0).is_ok());
assert!(check_latitude(-90.0).is_ok());
assert!(check_latitude(45.5).is_ok());
assert!(check_latitude(91.0).is_err());
assert!(check_latitude(-91.0).is_err());
assert!(check_latitude(f64::NAN).is_err());
assert!(check_latitude(f64::INFINITY).is_err());
}
#[test]
fn test_longitude_validation() {
assert!(check_longitude(0.0).is_ok());
assert!(check_longitude(180.0).is_ok());
assert!(check_longitude(-180.0).is_ok());
assert!(check_longitude(122.5).is_ok());
assert!(check_longitude(181.0).is_err());
assert!(check_longitude(-181.0).is_err());
assert!(check_longitude(f64::NAN).is_err());
assert!(check_longitude(f64::INFINITY).is_err());
}
#[test]
fn test_pressure_validation() {
assert!(check_pressure(1013.25).is_ok());
assert!(check_pressure(1000.0).is_ok());
assert!(check_pressure(500.0).is_ok());
assert!(check_pressure(0.0).is_err());
assert!(check_pressure(-100.0).is_err());
assert!(check_pressure(3000.0).is_err());
assert!(check_pressure(f64::NAN).is_err());
assert!(check_pressure(f64::INFINITY).is_err());
}
#[test]
fn test_elevation_angle_validation() {
assert!(check_elevation_angle(-90.0).is_ok());
assert!(check_elevation_angle(0.0).is_ok());
assert!(check_elevation_angle(90.0).is_ok());
assert!(check_elevation_angle(-91.0).is_err());
assert!(check_elevation_angle(91.0).is_err());
assert!(check_elevation_angle(f64::NAN).is_err());
assert!(check_elevation_angle(f64::INFINITY).is_err());
}
#[test]
fn test_temperature_validation() {
assert!(check_temperature(15.0).is_ok());
assert!(check_temperature(0.0).is_ok());
assert!(check_temperature(-40.0).is_ok());
assert!(check_temperature(50.0).is_ok());
assert!(check_temperature(-300.0).is_err());
assert!(check_temperature(150.0).is_err());
}
#[test]
#[cfg(feature = "std")]
fn test_error_display() {
let err = Error::InvalidLatitude { value: 95.0 };
assert_eq!(
err.to_string(),
"invalid latitude 95° (must be between -90° and +90°)"
);
let err = Error::InvalidLongitude { value: 185.0 };
assert_eq!(
err.to_string(),
"invalid longitude 185° (must be between -180° and +180°)"
);
let err = Error::ComputationError {
message: "convergence failed",
};
assert_eq!(err.to_string(), "computation error: convergence failed");
}
#[test]
fn test_check_azimuth() {
assert!(check_azimuth(0.0).is_ok());
assert!(check_azimuth(180.0).is_ok());
assert!(check_azimuth(360.0).is_ok());
assert!(check_azimuth(450.0).is_ok());
assert!(check_azimuth(-90.0).is_ok());
assert_eq!(check_azimuth(-90.0).unwrap(), 270.0);
assert_eq!(check_azimuth(450.0).unwrap(), 90.0);
assert!(check_azimuth(f64::NAN).is_err());
assert!(check_azimuth(f64::INFINITY).is_err());
}
#[test]
fn test_check_zenith_angle() {
assert!(check_zenith_angle(0.0).is_ok());
assert!(check_zenith_angle(90.0).is_ok());
assert!(check_zenith_angle(180.0).is_ok());
assert!(check_zenith_angle(-1.0).is_err());
assert!(check_zenith_angle(181.0).is_err());
assert!(check_zenith_angle(f64::NAN).is_err());
assert!(check_zenith_angle(f64::INFINITY).is_err());
}
}