1use std::collections::BTreeMap;
4
5use crate::astro::time::model::{Instant, TimeScale};
6use crate::id::GnssSatelliteId;
7use crate::observables::{
8 ObservableEphemerisSource, ObservableState, ObservableStateBatch, ObservablesError,
9};
10use crate::sp3::interp::{
11 fit_clock_spline_arcs, gather_sp3_precise_series, instant_to_j2000_seconds,
12 interpolate_precise_state, interpolate_precise_state_with_clock_arcs, ClockSplineArc,
13 PreciseSatSeries,
14};
15use crate::sp3::{
16 PreciseEphemerisSample, PreciseEphemerisSamples, PreciseSamplesError, Sp3, Sp3State,
17};
18use crate::{Error, Result};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum PreciseInterpolantError {
23 Samples(PreciseSamplesError),
25}
26
27impl core::fmt::Display for PreciseInterpolantError {
28 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
29 match self {
30 Self::Samples(err) => write!(f, "{err}"),
31 }
32 }
33}
34
35impl std::error::Error for PreciseInterpolantError {}
36
37impl From<PreciseSamplesError> for PreciseInterpolantError {
38 fn from(error: PreciseSamplesError) -> Self {
39 Self::Samples(error)
40 }
41}
42
43#[derive(Debug, Clone, PartialEq)]
50pub struct PreciseEphemerisInterpolant {
51 time_scale: TimeScale,
52 pub(super) nodes: BTreeMap<GnssSatelliteId, FittedPreciseSatSeries>,
53}
54
55#[derive(Debug, Clone, PartialEq)]
56pub(super) struct FittedPreciseSatSeries {
57 pub(super) series: PreciseSatSeries,
58 pub(super) clock_arcs: Vec<ClockSplineArc>,
59}
60
61impl FittedPreciseSatSeries {
62 pub(super) fn new(series: PreciseSatSeries) -> Self {
63 let clock_arcs = fit_clock_spline_arcs(&series.clk);
64 Self { series, clock_arcs }
65 }
66}
67
68impl PreciseEphemerisInterpolant {
69 pub fn from_sp3(source: &Sp3) -> Self {
77 let mut nodes = BTreeMap::new();
78 for &sat in source.satellites() {
79 let series = gather_sp3_precise_series(source, sat);
80 if !series.x.is_empty() {
81 nodes.insert(sat, FittedPreciseSatSeries::new(series));
82 }
83 }
84 Self {
85 time_scale: source.header.time_scale,
86 nodes,
87 }
88 }
89
90 pub fn from_samples(
98 samples: impl IntoIterator<Item = PreciseEphemerisSample>,
99 ) -> core::result::Result<Self, PreciseInterpolantError> {
100 let source = PreciseEphemerisSamples::from_samples(samples)?;
101 Ok(Self::from_precise_ephemeris_samples(&source))
102 }
103
104 pub fn from_precise_ephemeris_samples(source: &PreciseEphemerisSamples) -> Self {
106 Self {
107 time_scale: source.time_scale(),
108 nodes: source
109 .node_series()
110 .iter()
111 .map(|(&sat, series)| (sat, FittedPreciseSatSeries::new(series.clone())))
112 .collect(),
113 }
114 }
115
116 pub fn time_scale(&self) -> TimeScale {
118 self.time_scale
119 }
120
121 pub(super) fn node_series(&self) -> &BTreeMap<GnssSatelliteId, FittedPreciseSatSeries> {
122 &self.nodes
123 }
124
125 pub fn satellites(&self) -> impl Iterator<Item = GnssSatelliteId> + '_ {
127 self.nodes.keys().copied()
128 }
129
130 pub fn position_at_j2000_seconds(&self, sat: GnssSatelliteId, query: f64) -> Result<Sp3State> {
137 static EMPTY_F64: [f64; 0] = [];
138 static EMPTY_CLK: [(f64, f64, bool); 0] = [];
139 match self.nodes.get(&sat) {
140 Some(fitted) => interpolate_precise_state_with_clock_arcs(
141 sat,
142 &fitted.series.x,
143 &fitted.series.kx,
144 &fitted.series.ky,
145 &fitted.series.kz,
146 &fitted.clock_arcs,
147 query,
148 ),
149 None => interpolate_precise_state(
150 sat, &EMPTY_F64, &EMPTY_F64, &EMPTY_F64, &EMPTY_F64, &EMPTY_CLK, query,
151 ),
152 }
153 }
154
155 pub fn position(&self, sat: GnssSatelliteId, epoch: Instant) -> Result<Sp3State> {
160 if epoch.scale != self.time_scale {
161 return Err(Error::InvalidInput(format!(
162 "precise-interpolant query time scale {} does not match source time scale {}",
163 epoch.scale.abbrev(),
164 self.time_scale.abbrev()
165 )));
166 }
167 let query = instant_to_j2000_seconds(&epoch).ok_or(Error::EpochOutOfRange)?;
168 self.position_at_j2000_seconds(sat, query)
169 }
170
171 pub fn observable_states_at_j2000_s(
176 &self,
177 satellites: &[GnssSatelliteId],
178 epochs_j2000_s: &[f64],
179 ) -> core::result::Result<ObservableStateBatch, ObservablesError> {
180 <Self as ObservableEphemerisSource>::observable_states_at_j2000_s(
181 self,
182 satellites,
183 epochs_j2000_s,
184 )
185 }
186
187 pub fn observable_states_at_shared_j2000_s(
192 &self,
193 satellites: &[GnssSatelliteId],
194 epoch_j2000_s: f64,
195 ) -> ObservableStateBatch {
196 <Self as ObservableEphemerisSource>::observable_states_at_shared_j2000_s(
197 self,
198 satellites,
199 epoch_j2000_s,
200 )
201 }
202}
203
204impl ObservableEphemerisSource for PreciseEphemerisInterpolant {
205 fn observable_state_at_j2000_s(
206 &self,
207 sat: GnssSatelliteId,
208 t_j2000_s: f64,
209 ) -> core::result::Result<ObservableState, ObservablesError> {
210 let state = self
211 .position_at_j2000_seconds(sat, t_j2000_s)
212 .map_err(ObservablesError::Ephemeris)?;
213 Ok(ObservableState {
214 position_ecef_m: state.position.as_array(),
215 clock_s: state.clock_s,
216 })
217 }
218}