1use log::{debug, error, info};
2
3use anise::{
4 math::Vector3,
5 prelude::{Almanac, Frame},
6};
7
8use crate::{
9 bancroft::Bancroft,
10 candidate::Candidate,
11 cfg::Config,
12 ephemeris::EphemerisSource,
13 navigation::{apriori::Apriori, solutions::PVTSolution, state::State, Navigation},
14 orbit::OrbitSource,
15 pool::Pool,
16 prelude::{EnvironmentalBias, Epoch, Error, Rc, SpacebornBias, UserParameters},
17 rtk::{NullRTK, RTKBase},
18 time::AbsoluteTime,
19};
20
21pub struct Solver<
30 EPH: EphemerisSource,
31 ORB: OrbitSource,
32 EB: EnvironmentalBias,
33 SB: SpacebornBias,
34 TIM: AbsoluteTime,
35> {
36 pub cfg: Config,
38
39 earth_cef: Frame,
41
42 rover_pool: Pool<EPH, ORB, EB, SB>,
44
45 base_pool: Pool<EPH, ORB, EB, SB>,
47
48 navigation: Navigation,
52
53 absolute_time: TIM,
55
56 initial_ecef_m: Option<Vector3>,
58}
59
60impl<
61 EPH: EphemerisSource,
62 ORB: OrbitSource,
63 EB: EnvironmentalBias,
64 SB: SpacebornBias,
65 TIM: AbsoluteTime,
66 > Solver<EPH, ORB, EB, SB, TIM>
67{
68 pub fn new(
85 almanac: Almanac,
86 earth_cef: Frame,
87 cfg: Config,
88 eph_source: Rc<EPH>,
89 orbit_source: Rc<ORB>,
90 spaceborn_biases: Rc<SB>,
91 environmental_biases: Rc<EB>,
92 absolute_time: TIM,
93 state_ecef_m: Option<(f64, f64, f64)>,
94 ) -> Self {
95 let initial_ecef_m = match state_ecef_m {
96 Some((x0, y0, z0)) => Some(Vector3::new(x0, y0, z0)),
97 _ => None,
98 };
99
100 let navigation = Navigation::new(&cfg, earth_cef);
101
102 let rover_pool = Pool::allocate(
103 almanac.clone(),
104 cfg.clone(),
105 earth_cef,
106 eph_source.clone(),
107 orbit_source.clone(),
108 environmental_biases.clone(),
109 spaceborn_biases.clone(),
110 );
111
112 let base_pool = Pool::allocate(
113 almanac,
114 cfg.clone(),
115 earth_cef,
116 eph_source,
117 orbit_source,
118 environmental_biases,
119 spaceborn_biases,
120 );
121
122 Self {
123 earth_cef,
124 navigation,
125 rover_pool,
126 base_pool,
127 absolute_time,
128 initial_ecef_m,
129 cfg: cfg.clone(),
130 }
131 }
132
133 pub fn new_survey(
137 almanac: Almanac,
138 earth_cef: Frame,
139 cfg: Config,
140 eph_source: Rc<EPH>,
141 orbit_source: Rc<ORB>,
142 spaceborn_biases: Rc<SB>,
143 environmental_biases: Rc<EB>,
144 absolute_time: TIM,
145 ) -> Self {
146 Self::new(
147 almanac,
148 earth_cef,
149 cfg,
150 eph_source,
151 orbit_source,
152 spaceborn_biases,
153 environmental_biases,
154 absolute_time,
155 None,
156 )
157 }
158
159 pub fn ppp(
174 &mut self,
175 epoch: Epoch,
176 params: UserParameters,
177 candidates: &[Candidate],
178 ) -> Result<PVTSolution, Error> {
179 let null_rtk = NullRTK {};
180 let solution = self.solve(epoch, params, candidates, &null_rtk, false)?;
181 Ok(solution)
182 }
183
184 pub fn rtk<RTK: RTKBase>(
208 &mut self,
209 epoch: Epoch,
210 params: UserParameters,
211 candidates: &[Candidate],
212 rtk_base: &RTK,
213 ) -> Result<PVTSolution, Error> {
214 self.solve(epoch, params, candidates, rtk_base, true)
215 }
216
217 fn solve<RTK: RTKBase>(
219 &mut self,
220 epoch: Epoch,
221 params: UserParameters,
222 pool: &[Candidate],
223 rtk_base: &RTK,
224 uses_rtk: bool,
225 ) -> Result<PVTSolution, Error> {
226 let rtk_base_name = rtk_base.name();
227 let min_required = self.min_sv_required(uses_rtk);
228
229 if pool.len() < min_required {
230 return Err(Error::NotEnoughCandidates);
232 }
233
234 self.rover_pool.new_epoch(pool);
235
236 if uses_rtk {
237 let observations = rtk_base.observe(epoch);
238 info!("{epoch} - using remote {rtk_base_name} reference");
239 self.base_pool.new_epoch(&observations);
240 }
241
242 self.rover_pool.pre_fit("rover", &self.absolute_time);
243
244 if uses_rtk {
245 self.base_pool.pre_fit(&rtk_base_name, &self.absolute_time);
246 }
247
248 if self.rover_pool.len() < min_required {
249 return Err(Error::NotEnoughPreFitCandidates);
250 }
251
252 let epoch = if epoch.time_scale == self.cfg.timescale {
253 epoch
254 } else {
255 let corrected = self
256 .absolute_time
257 .epoch_correction(epoch, self.cfg.timescale);
258
259 debug!(
260 "{} - |{}-{}| corrected sampling: {}",
261 epoch, epoch.time_scale, self.cfg.timescale, corrected
262 );
263
264 corrected
265 };
266
267 self.rover_pool.orbital_states_fit("rover");
268
269 if uses_rtk {
270 self.base_pool.orbital_states_fit(&rtk_base_name);
271 }
272
273 let state = if self.navigation.is_initialized() {
275 self.navigation.state.with_epoch(epoch)
276 } else {
277 match self.initial_ecef_m {
278 Some(x0_y0_z0_m) => {
279 let apriori = Apriori::from_ecef_m(x0_y0_z0_m, epoch, self.earth_cef);
280
281 let state = State::from_apriori(&apriori).unwrap_or_else(|e| {
282 panic!("Solver initial preset is physically incorrect: {e}");
283 });
284
285 debug!("{epoch} - initial state: {state}");
286 state
287 },
288 None => {
289 let solver = Bancroft::new(self.rover_pool.candidates())?;
290 let solution = solver.resolve()?;
291 let x0_y0_z0_m = Vector3::new(solution[0], solution[1], solution[2]);
292
293 let apriori = Apriori::from_ecef_m(x0_y0_z0_m, epoch, self.earth_cef);
294
295 let state = State::from_apriori(&apriori).unwrap_or_else(|e| {
296 panic!("Solver failed to initialize itself. Physical error: {e}");
297 });
298
299 debug!("{epoch} - initial state: {state}");
300 state
301 },
302 }
303 };
304
305 self.rover_pool.post_fit("rover", &state).map_err(|e| {
307 error!("{epoch} rover postfit error {e}");
308 Error::PostfitPrenav
309 })?;
310
311 let double_differences = if uses_rtk {
312 self.base_pool
314 .post_fit(&rtk_base_name, &state)
315 .map_err(|e| {
316 error!("{epoch} {rtk_base_name} postfit error: {e}");
317 Error::PostfitPrenav
318 })?;
319
320 match self.rover_pool.rtk_post_fit(&mut self.base_pool) {
322 Ok(double_differences) => Some(double_differences),
323 Err(e) => {
324 error!("{epoch} - rtk post-fit error: {e}");
325 return Err(e);
326 },
327 }
328 } else {
329 None
330 };
331
332 let pool_size = self.rover_pool.len();
333
334 if pool_size < min_required {
335 return Err(Error::NotEnoughPostFitCandidates);
336 }
337
338 match self.navigation.solve(
340 epoch,
341 params,
342 &state,
343 self.rover_pool.candidates(),
344 pool_size,
345 uses_rtk,
346 rtk_base,
347 &self.rover_pool.pivot_position_ecef_m,
348 &double_differences,
349 ) {
350 Ok(_) => {
351 info!("{epoch} - sucess");
352 },
353 Err(e) => {
354 error!("{epoch} - iteration failure: {e}");
355 return Err(e);
356 },
357 }
358
359 let solution = PVTSolution::new(
361 epoch,
362 uses_rtk,
363 &self.navigation.state,
364 &self.navigation.dop,
365 &self.navigation.sv,
366 );
367
368 if self.cfg.solver.open_loop {
370 self.navigation.state = state;
371 }
372
373 Ok(solution)
374 }
375
376 pub fn reset(&mut self) {
378 self.navigation.reset();
379 }
380
381 fn min_sv_required(&self, uses_rtk: bool) -> usize {
383 let mut min_sv = 4;
384
385 if self.navigation.is_initialized() && self.cfg.fixed_altitude.is_some() {
386 min_sv -= 1;
387 }
388
389 if uses_rtk {
390 min_sv += 1;
391 }
392
393 min_sv
394 }
395}