use core::fmt;
use crate::angles::{Latitude, Longitude};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct ObserverLocation {
pub latitude: Latitude,
pub longitude: Longitude,
pub elevation_m: Option<f64>,
}
impl ObserverLocation {
pub const fn new(latitude: Latitude, longitude: Longitude, elevation_m: Option<f64>) -> Self {
Self {
latitude,
longitude,
elevation_m,
}
}
pub fn validate(&self) -> Result<(), ObserverLocationValidationError> {
let latitude = self.latitude.degrees();
if !latitude.is_finite() {
return Err(ObserverLocationValidationError::NonFiniteLatitude { value: latitude });
}
if !(-90.0..=90.0).contains(&latitude) {
return Err(ObserverLocationValidationError::LatitudeOutOfRange { value: latitude });
}
let longitude = self.longitude.degrees();
if !longitude.is_finite() {
return Err(ObserverLocationValidationError::NonFiniteLongitude { value: longitude });
}
if let Some(elevation_m) = self.elevation_m {
if !elevation_m.is_finite() {
return Err(ObserverLocationValidationError::NonFiniteElevation {
value: elevation_m,
});
}
}
Ok(())
}
pub fn summary_line(&self) -> String {
let elevation = self
.elevation_m
.map(|value| format!("{value:.3} m"))
.unwrap_or_else(|| "n/a".to_string());
format!(
"latitude={}, longitude={}, elevation={}",
self.latitude, self.longitude, elevation
)
}
pub fn validated_summary_line(&self) -> Result<String, ObserverLocationValidationError> {
self.validate()?;
Ok(self.summary_line())
}
}
impl fmt::Display for ObserverLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.summary_line())
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum ObserverLocationValidationError {
NonFiniteLatitude {
value: f64,
},
LatitudeOutOfRange {
value: f64,
},
NonFiniteLongitude {
value: f64,
},
NonFiniteElevation {
value: f64,
},
}
impl ObserverLocationValidationError {
pub fn summary_line(&self) -> String {
match self {
Self::NonFiniteLatitude { value } => {
format!("observer latitude must be finite, got {value}")
}
Self::LatitudeOutOfRange { value } => {
format!("observer latitude must stay within [-90, 90], got {value}")
}
Self::NonFiniteLongitude { value } => {
format!("observer longitude must be finite, got {value}")
}
Self::NonFiniteElevation { value } => {
format!("observer elevation must be finite, got {value}")
}
}
}
}
impl fmt::Display for ObserverLocationValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.summary_line())
}
}
impl std::error::Error for ObserverLocationValidationError {}