use super::body::{BodySurfaceModel, LatitudeConvention};
use super::error::SurfaceError;
use super::reference_surface::ReferenceSurface;
use super::site::SurfaceSite;
use crate::coordinates::centers::ReferenceCenter;
use crate::qtty::{Degrees, Meters};
use affn::AffineCenter;
use core::marker::PhantomData;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", serde(bound(serialize = "")))]
pub struct SurfaceObserverParams<B: BodySurfaceModel> {
lon: Degrees,
lat: Degrees,
height: Meters,
surface: ReferenceSurface,
latitude_convention: LatitudeConvention,
#[cfg_attr(feature = "serde", serde(skip))]
_body: PhantomData<B>,
}
#[cfg(feature = "serde")]
impl<'de, B: BodySurfaceModel> Deserialize<'de> for SurfaceObserverParams<B> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
struct Fields {
lon: Degrees,
lat: Degrees,
height: Meters,
surface: ReferenceSurface,
latitude_convention: LatitudeConvention,
}
let site = Fields::deserialize(deserializer)?;
let site = SurfaceSite::<B>::with_surface(
site.lon,
site.lat,
site.height,
site.surface,
site.latitude_convention,
)
.map_err(serde::de::Error::custom)?;
Ok(Self::from_site(&site))
}
}
impl<B: BodySurfaceModel> PartialEq for SurfaceObserverParams<B> {
fn eq(&self, other: &Self) -> bool {
self.lon == other.lon
&& self.lat == other.lat
&& self.height == other.height
&& self.surface == other.surface
&& self.latitude_convention == other.latitude_convention
}
}
impl<B: BodySurfaceModel> SurfaceObserverParams<B> {
pub fn lon(&self) -> Degrees {
self.lon
}
pub fn lat(&self) -> Degrees {
self.lat
}
pub fn height(&self) -> Meters {
self.height
}
pub fn surface(&self) -> &ReferenceSurface {
&self.surface
}
pub fn latitude_convention(&self) -> LatitudeConvention {
self.latitude_convention
}
pub fn from_site(site: &SurfaceSite<B>) -> Self {
Self {
lon: site.lon(),
lat: site.lat(),
height: site.height(),
surface: *site.surface(),
latitude_convention: site.latitude_convention(),
_body: PhantomData,
}
}
pub fn try_origin() -> Result<Self, SurfaceError> {
let site = SurfaceSite::<B>::new(Degrees::new(0.0), Degrees::new(0.0), Meters::new(0.0))?;
Ok(Self::from_site(&site))
}
}
impl<B: BodySurfaceModel> Default for SurfaceObserverParams<B> {
fn default() -> Self {
Self::try_origin().unwrap_or_else(|err| {
panic!(
"BodySurfaceModel::default_surface for {} must admit an origin site: {err}",
B::body_name()
)
})
}
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SurfaceObserver<B: BodySurfaceModel>(PhantomData<B>);
impl<B: BodySurfaceModel> ReferenceCenter for SurfaceObserver<B> {
type Params = SurfaceObserverParams<B>;
fn center_name() -> &'static str {
"SurfaceObserver"
}
}
impl<B: BodySurfaceModel> AffineCenter for SurfaceObserver<B> {}
impl<B: BodySurfaceModel> SurfaceObserver<B> {
#[inline]
pub fn body_name() -> &'static str {
B::body_name()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bodies::solar_system::Moon;
use crate::qtty::{DEG, M};
#[cfg(feature = "serde")]
#[test]
fn observer_params_deserialization_enforces_site_invariants() {
let site = SurfaceSite::<Moon>::new(10.0 * DEG, 20.0 * DEG, 0.0 * M).unwrap();
let mut payload = serde_json::to_value(site.to_params()).unwrap();
payload["lat"] = serde_json::to_value(95.0 * DEG).unwrap();
assert!(serde_json::from_value::<SurfaceObserverParams<Moon>>(payload).is_err());
}
#[test]
fn center_name_is_stable() {
assert_eq!(
<SurfaceObserver<Moon> as ReferenceCenter>::center_name(),
"SurfaceObserver"
);
assert_eq!(SurfaceObserver::<Moon>::body_name(), "Moon");
}
#[test]
fn observer_params_getters_preserve_site_and_try_origin() {
let site = SurfaceSite::<Moon>::new(30.0 * DEG, 20.0 * DEG, -10.0 * M).unwrap();
let params = SurfaceObserverParams::from_site(&site);
assert_eq!(params.lon(), site.lon());
assert_eq!(params.lat(), site.lat());
assert_eq!(params.height(), site.height());
assert_eq!(params.surface(), site.surface());
assert_eq!(params.latitude_convention(), site.latitude_convention());
assert_eq!(params, site.to_params());
let origin = SurfaceObserverParams::<Moon>::try_origin().unwrap();
assert_eq!(origin.lon().value(), 0.0);
assert_eq!(origin.lat().value(), 0.0);
assert_eq!(origin.height().value(), 0.0);
assert_eq!(origin, SurfaceObserverParams::<Moon>::default());
}
}