1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
use log::{debug, error, info};
use anise::{
math::Vector3,
prelude::{Almanac, Frame},
};
use crate::{
bancroft::Bancroft,
candidate::Candidate,
cfg::Config,
ephemeris::EphemerisSource,
navigation::{apriori::Apriori, solutions::PVTSolution, state::State, Navigation},
orbit::OrbitSource,
pool::Pool,
prelude::{EnvironmentalBias, Epoch, Error, Rc, SpacebornBias, UserParameters},
rtk::{NullRTK, RTKBase},
time::AbsoluteTime,
};
/// [Solver] to resolve [PVTSolution]s.
///
/// ## Generics:
/// - EPH: [EphemerisSource] custom data source. Curreuntly unused: limited to [ORB] only!
/// - ORB: [OrbitSource], custom Orbit data source.
/// - EB: [EnvironmentalBias] implementation using either intenral or custom external model.
/// - SB: [SpacebornBias] external information provider.
/// - TIM: [AbsoluteTime] source for correct absolute time management.
pub struct Solver<
EPH: EphemerisSource,
ORB: OrbitSource,
EB: EnvironmentalBias,
SB: SpacebornBias,
TIM: AbsoluteTime,
> {
/// Solver [Config]uration
pub cfg: Config,
/// [Frame]
earth_cef: Frame,
/// Rover pool
rover_pool: Pool<EPH, ORB, EB, SB>,
/// Base pool
base_pool: Pool<EPH, ORB, EB, SB>,
/// PPP prefit
/// [Navigation] solver
navigation: Navigation,
/// [AbsoluteTime] implementation
absolute_time: TIM,
/// Possible initial position
initial_ecef_m: Option<Vector3>,
}
impl<
EPH: EphemerisSource,
ORB: OrbitSource,
EB: EnvironmentalBias,
SB: SpacebornBias,
TIM: AbsoluteTime,
> Solver<EPH, ORB, EB, SB, TIM>
{
/// Creates a new [Solver] for either direct or differential navigation,
/// with possible apriori knowledge.
///
/// ## Input
/// - almanac: provided valid [Almanac]
/// - earth_cef: [Frame] that must be an ECEF
/// - cfg: solver [Config]uration
/// - eph_source: custom [EphemerisSource], unavailable right now,
/// tie to null.
/// - orbit_source: custom [OrbitSource] implementation,
/// wrapped in a Rc<RefCell<>> which allows the solver
/// and the orbital provider to live in the same thread.
/// - absolute_time: external [AbsoluteTime] implementation.
/// - bias: [Bias] model implementation
/// - state_ecef_m: provide initial state as ECEF 3D coordinates,
/// otherwise we will have to figure them.
pub fn new(
almanac: Almanac,
earth_cef: Frame,
cfg: Config,
eph_source: Rc<EPH>,
orbit_source: Rc<ORB>,
spaceborn_biases: Rc<SB>,
environmental_biases: Rc<EB>,
absolute_time: TIM,
state_ecef_m: Option<(f64, f64, f64)>,
) -> Self {
let initial_ecef_m = match state_ecef_m {
Some((x0, y0, z0)) => Some(Vector3::new(x0, y0, z0)),
_ => None,
};
let navigation = Navigation::new(&cfg, earth_cef);
let rover_pool = Pool::allocate(
almanac.clone(),
cfg.clone(),
earth_cef,
eph_source.clone(),
orbit_source.clone(),
environmental_biases.clone(),
spaceborn_biases.clone(),
);
let base_pool = Pool::allocate(
almanac,
cfg.clone(),
earth_cef,
eph_source,
orbit_source,
environmental_biases,
spaceborn_biases,
);
Self {
earth_cef,
navigation,
rover_pool,
base_pool,
absolute_time,
initial_ecef_m,
cfg: cfg.clone(),
}
}
/// Creates a new [Solver] to operate without a priori knowledge
/// and perform a complete survey from scratch. Refer to [Self::new]
/// for more information.
pub fn new_survey(
almanac: Almanac,
earth_cef: Frame,
cfg: Config,
eph_source: Rc<EPH>,
orbit_source: Rc<ORB>,
spaceborn_biases: Rc<SB>,
environmental_biases: Rc<EB>,
absolute_time: TIM,
) -> Self {
Self::new(
almanac,
earth_cef,
cfg,
eph_source,
orbit_source,
spaceborn_biases,
environmental_biases,
absolute_time,
None,
)
}
/// [PVTSolution] solving attempt using PPP technique (no reference).
/// Unlike RTK resolution attempt, PPP solving will resolve both
/// the position and the local clock state, but it is
/// a less accurate and much more complex process.
///
/// ## Input
/// - epoch: [Epoch] of measurement
/// - params: [UserParameters]
/// - candidates: proposed [Candidate]s (= measurements)
/// - rtk_base: possible [RTKBase] we will connect to
///
/// ## Output
/// - success: [PVTSolution]
/// - failure: [Error]
pub fn ppp(
&mut self,
epoch: Epoch,
params: UserParameters,
candidates: &[Candidate],
) -> Result<PVTSolution, Error> {
let null_rtk = NullRTK {};
let solution = self.solve(epoch, params, candidates, &null_rtk, false)?;
Ok(solution)
}
/// [PVTSolution] solving attempt using RTK technique and a single remote
/// reference site that implements [RTKBase].
/// You may catch RTK solving issues (for example, network loss)
/// and retry using [Self::ppp], because this object is compatible
/// with both. For this function to converge, the remote
/// site and rover proposals must agree in their content (enough matches)
/// and signals must fulfill the navigation [Method] being used.
///
/// NB: unlike PPP solving, RTK solving will only resolve the spatial
/// state, the clock state can only remain undetermined. If you are
/// interested by both, you will either have to restrict to [Self::ppp_run],
/// or do a PPP (time only) run after the RTK run.
///
/// ## Input
/// - epoch: [Epoch] of measurement from the rover reported by the rover.
/// The remote site must match the sampling instant closely, reducing
/// the time-difference (RTK aging).
/// - profile: rover [UserProfile]
/// - candidates: rover measurements, wrapped as [Candidate]s.
/// - rtk_base: remote reference site that implements [RTKBase]reference.
///
/// ## Output
/// - [PVTSolution].
pub fn rtk<RTK: RTKBase>(
&mut self,
epoch: Epoch,
params: UserParameters,
candidates: &[Candidate],
rtk_base: &RTK,
) -> Result<PVTSolution, Error> {
self.solve(epoch, params, candidates, rtk_base, true)
}
/// [PVTSolution] solving attempt.
fn solve<RTK: RTKBase>(
&mut self,
epoch: Epoch,
params: UserParameters,
pool: &[Candidate],
rtk_base: &RTK,
uses_rtk: bool,
) -> Result<PVTSolution, Error> {
let rtk_base_name = rtk_base.name();
let min_required = self.min_sv_required(uses_rtk);
if pool.len() < min_required {
// no need to proceed further
return Err(Error::NotEnoughCandidates);
}
self.rover_pool.new_epoch(pool);
if uses_rtk {
let observations = rtk_base.observe(epoch);
info!("{epoch} - using remote {rtk_base_name} reference");
self.base_pool.new_epoch(&observations);
}
self.rover_pool.pre_fit("rover", &self.absolute_time);
if uses_rtk {
self.base_pool.pre_fit(&rtk_base_name, &self.absolute_time);
}
if self.rover_pool.len() < min_required {
return Err(Error::NotEnoughPreFitCandidates);
}
let epoch = if epoch.time_scale == self.cfg.timescale {
epoch
} else {
let corrected = self
.absolute_time
.epoch_correction(epoch, self.cfg.timescale);
debug!(
"{} - |{}-{}| corrected sampling: {}",
epoch, epoch.time_scale, self.cfg.timescale, corrected
);
corrected
};
self.rover_pool.orbital_states_fit("rover");
if uses_rtk {
self.base_pool.orbital_states_fit(&rtk_base_name);
}
// current state
let state = if self.navigation.is_initialized() {
self.navigation.state.with_epoch(epoch)
} else {
match self.initial_ecef_m {
Some(x0_y0_z0_m) => {
let apriori = Apriori::from_ecef_m(x0_y0_z0_m, epoch, self.earth_cef);
let state = State::from_apriori(&apriori).unwrap_or_else(|e| {
panic!("Solver initial preset is physically incorrect: {e}");
});
debug!("{epoch} - initial state: {state}");
state
},
None => {
let solver = Bancroft::new(self.rover_pool.candidates())?;
let solution = solver.resolve()?;
let x0_y0_z0_m = Vector3::new(solution[0], solution[1], solution[2]);
let apriori = Apriori::from_ecef_m(x0_y0_z0_m, epoch, self.earth_cef);
let state = State::from_apriori(&apriori).unwrap_or_else(|e| {
panic!("Solver failed to initialize itself. Physical error: {e}");
});
debug!("{epoch} - initial state: {state}");
state
},
}
};
// rover post-fit
self.rover_pool.post_fit("rover", &state).map_err(|e| {
error!("{epoch} rover postfit error {e}");
Error::PostfitPrenav
})?;
let double_differences = if uses_rtk {
// base post-fit
self.base_pool
.post_fit(&rtk_base_name, &state)
.map_err(|e| {
error!("{epoch} {rtk_base_name} postfit error: {e}");
Error::PostfitPrenav
})?;
// special RTK post-fit
match self.rover_pool.rtk_post_fit(&mut self.base_pool) {
Ok(double_differences) => Some(double_differences),
Err(e) => {
error!("{epoch} - rtk post-fit error: {e}");
return Err(e);
},
}
} else {
None
};
let pool_size = self.rover_pool.len();
if pool_size < min_required {
return Err(Error::NotEnoughPostFitCandidates);
}
// Solving attempt
match self.navigation.solve(
epoch,
params,
&state,
self.rover_pool.candidates(),
pool_size,
uses_rtk,
rtk_base,
&self.rover_pool.pivot_position_ecef_m,
&double_differences,
) {
Ok(_) => {
info!("{epoch} - sucess");
},
Err(e) => {
error!("{epoch} - iteration failure: {e}");
return Err(e);
},
}
// Form P.V.T solution
let solution = PVTSolution::new(
epoch,
uses_rtk,
&self.navigation.state,
&self.navigation.dop,
&self.navigation.sv,
);
// Special "open loop" option
if self.cfg.solver.open_loop {
self.navigation.state = state;
}
Ok(solution)
}
/// Reset this [Solver].
pub fn reset(&mut self) {
self.navigation.reset();
}
/// Returns minimal requirement for current preset
fn min_sv_required(&self, uses_rtk: bool) -> usize {
let mut min_sv = 4;
if self.navigation.is_initialized() && self.cfg.fixed_altitude.is_some() {
min_sv -= 1;
}
if uses_rtk {
min_sv += 1;
}
min_sv
}
}