diff --git a/Cargo.toml b/Cargo.toml
index 20e28b4..aed737e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -41,7 +41,8 @@ cggtts = [
]
[dependencies.rinex]
-version = "0.20"
+git = "https://github.com/nav-solutions/rinex"
+rev = "a10b41abb0ccd6b804dff6acd560992cdcd4c639"
features = [
"qc",
"processing",
@@ -50,13 +51,13 @@ features = [
"ut1",
"meteo",
"clock",
- "ionex",
"antex",
"serde",
]
[dependencies.gnss-qc]
-version = "0.4"
+git = "https://github.com/nav-solutions/gnss-qc"
+rev = "3ee5918a37bf2b18371bd18d9a40b2742004865f"
features = [
"sp3",
"navigation",
@@ -64,12 +65,11 @@ features = [
]
[dependencies.gnss-rtk]
-git = "https://github.com/rtk-rs/gnss-rtk"
-rev = "5eb681c3f6f123d36b1c5b46cf27429bcf45f3f7"
+git = "https://github.com/nav-solutions/gnss-rtk"
+rev = "bf0914fb9134eef35a3ef73bc105d5ec28fa783b"
optional = true
features = [
"serde",
- "embed_ephem"
]
[dependencies]
@@ -89,6 +89,6 @@ csv = { version = "1.3", optional = true }
gnss-rs = { version = "2.4", features = ["serde"] }
clap = { version = "4.4.13", features = ["derive", "color"] }
hifitime = { version = "4.1", features = ["serde", "std"] }
-anise = { version = "0.6", features = ["embed_ephem"] }
+anise = { version = "0.5.3", features = ["embed_ephem"] }
serde = { version = "1.0", default-features = false, features = ["derive"] }
cggtts = { version = "4.3", features = ["serde", "scheduler", "tracker"], optional = true }
diff --git a/README.md b/README.md
index 9ecc66b..3489359 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ RINEX-Cli
[](https://github.com/rust-lang/rust/releases/tag/1.82.0)
[](https://github.com/rtk-rs/rinex-cli/blob/main/LICENSE)
-`rinex-cli` is a command line tool to post process RINEX and SP3 files.
+`rinex-cli` is a command line tool to post-process RINEX and SP3 files.
Because RINEX and SP3 cover many applications, `rinex-cli` can be used for many applications.
The most important being:
@@ -22,8 +22,10 @@ The most important being:
- generate high level reports
- Synthesis
- generate RINEX (and soon SP3) from provided products
-- Post processed navigation (`ppp` mode) because it integrates a complete
-PVT solver (on `ppp` feature only)
+- post-processed navigation (on `ppp` feature only) because it integrates a complete
+ - RINEX-Cli integrates a complete [P.V.T solution solver](https://github.com/rtk-rs/gnss-rtk)
+ - absolute navigation with `ppp` command line
+ - differential navigation with `rtk` command line, using one ground station
- CGGTTS solutions solver (`ppp --cggtts` mode) by combining the `ppp` **and** `cggtts` options
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
index 9861ac3..3b1e35c 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -57,8 +57,8 @@ pub struct Context {
pub workspace: Workspace,
#[cfg(feature = "ppp")]
- /// (RX) [Orbit] to use, whether is was automatically picked up,
- /// or manually overwritten.
+ /// (RX) [Orbit] to be used in several proceeses.
+ /// Either automatically picked up, manually assigned or overwritten, or non-existing.
pub rx_orbit: Option
,
}
@@ -330,33 +330,6 @@ Otherwise it gets automatically picked up."))
}
}
- /// Returns individual input BASE STATION -d
- pub fn base_station_directories(&self) -> Vec<&String> {
- match self.matches.subcommand() {
- Some(("rtk", submatches)) => {
- if let Some(dir) = submatches.get_many::("dir") {
- dir.collect()
- } else {
- Vec::new()
- }
- },
- _ => Vec::new(),
- }
- }
- /// Returns individual input BASE STATION -fp
- pub fn base_station_files(&self) -> Vec<&String> {
- match self.matches.subcommand() {
- Some(("rtk", submatches)) => {
- if let Some(fp) = submatches.get_many::("fp") {
- fp.collect()
- } else {
- Vec::new()
- }
- },
- _ => Vec::new(),
- }
- }
-
/// Returns list of preprocessing operations
pub fn preprocessing(&self) -> Vec<&String> {
if let Some(filters) = self.matches.get_many::("preprocessing") {
diff --git a/src/cli/positioning.rs b/src/cli/positioning.rs
index 1701498..629f46f 100644
--- a/src/cli/positioning.rs
+++ b/src/cli/positioning.rs
@@ -12,8 +12,8 @@ fn shared_args(cmd: Command) -> Command {
.action(ArgAction::Append)
.help("Position Solver configuration file (JSON). See --help.")
.long_help("
-Refer to all our navigation demos (subfolder).
-[https://docs.rs/gnss-rtk/latest/gnss_rtk/prelude/struct.Config.html] is the structure to represent in JSON.
+Refer to all our navigation demos (subfolder) and example scripts.
+[https://docs.rs/gnss-rtk/latest/gnss_rtk/prelude/struct.Config.html] is the structure to be descrined.
"));
let cmd = cmd.next_help_heading("User / Rover Profile")
@@ -21,15 +21,44 @@ Refer to all our navigation demos (subfolder).
Arg::new("static")
.long("static")
.action(ArgAction::SetTrue)
- .help("Define that the user (rover) is static. Antenna was held static for the entire session."))
- .arg(
- Arg::new("clock-sigma")
- .long("clock-sigma")
- .action(ArgAction::Set)
- .value_parser(value_parser!(f64))
- .required(false)
- .help("Define the uncertainty/bias over next clock state prediction (in seconds).
-Default value is 10ms (=low quality clock).")
+ .help("Define the rover as static, meaning, its antenna was held static for the entire session.
+The default profile is \"pedestrian\" (very low velocity), which is not suited for very fast moving rovers."))
+ .arg(
+ Arg::new("car")
+ .long("car")
+ .action(ArgAction::SetTrue)
+ .help("Define car profile (low velocity).
+The default profile is \"pedestrian\" (very low velocity), which is not suited for very fast moving rovers."))
+ .arg(
+ Arg::new("airplane")
+ .long("airplane")
+ .action(ArgAction::SetTrue)
+ .help("Define airplane profile (high velocity).
+The default profile is \"pedestrian\" (very low velocity), which is not suited for very fast moving rovers."))
+ .arg(
+ Arg::new("rocket")
+ .long("rocket")
+ .action(ArgAction::SetTrue)
+ .help("Define rocket profile (very high velocity).
+The default profile is \"pedestrian\" (very low velocity), which is not suited for very fast moving rovers."))
+ .arg(
+ Arg::new("quartz")
+ .long("quartz")
+ .action(ArgAction::SetTrue)
+ .help("Define quartz (rover clock) profile (very poor quality).
+The default profile is Oscillator/OCXO."))
+.arg(
+ Arg::new("atomic")
+ .long("quartz")
+ .action(ArgAction::SetTrue)
+ .help("Define atomic (rover clock) profile (high quality, at the scale of a GNSS constellation).
+The default profile is Oscillator/OCXO."))
+.arg(
+ Arg::new("h-maser")
+ .long("h-maser")
+ .action(ArgAction::SetTrue)
+ .help("Define Hydrogen MASER (rover clock) profile (ultra high quality, better than GNSS constellation).
+The default profile is Oscillator/OCXO.")
);
let cmd = cmd.next_help_heading("Solutions formating");
@@ -66,45 +95,6 @@ Default value is 10ms (=low quality clock).")
)
};
- let cmd = cmd.next_help_heading("CGGTTS Post FIT");
-
- let cmd = if cfg!(not(feature = "cggtts")) {
- cmd.arg(
- Arg::new("cggtts")
- .long("cggtts")
- .action(ArgAction::SetTrue)
- .help("[NOT AVAILABLE] requires cggtts compilation option"),
- )
- } else {
- cmd
- .arg(Arg::new("cggtts")
- .long("cggtts")
- .action(ArgAction::SetTrue)
- .help("Activate CGGTTS special Post FIT"))
- .arg(Arg::new("tracking")
- .long("trk")
- .value_parser(value_parser!(Duration))
- .action(ArgAction::Set)
- .help("CGGTTS custom tracking duration.
- Otherwise, the default tracking duration is used. Refer to [https://docs.rs/cggtts/latest/cggtts/track/struct.Scheduler.html]."))
- .arg(Arg::new("lab")
- .long("lab")
- .action(ArgAction::Set)
- .help("Define the name of your station or laboratory here."))
- .arg(Arg::new("utck")
- .long("utck")
- .action(ArgAction::Set)
- .conflicts_with("clock")
- .help("If the local clock tracks a local UTC replica, you can define the name
- of this replica here."))
- .arg(Arg::new("clock")
- .long("clk")
- .action(ArgAction::Set)
- .conflicts_with("utck")
- .help("If the local clock is not a UTC replica and has a specific name, you
- can define it here."))
- };
-
cmd
}
@@ -112,7 +102,7 @@ pub fn ppp_subcommand() -> Command {
let cmd = Command::new("ppp")
.arg_required_else_help(false)
.about(
- "Post Processed Positioning. Use this mode to deploy the precise position solver.
+ "Post-processed PPP (absolute navigation).
The solutions are added to the final report as an extra chapter. See --help",
)
.long_about(
@@ -120,7 +110,46 @@ The solutions are added to the final report as an extra chapter. See --help",
PVT solutions from RINEX data sampled by a single receiver (! This is not RTK!).
The solutions are presented in the analysis report (post processed results chapter).
Use --cggtts to convert solutions to CGGTTS special format.",
- );
+ )
+ .next_help_heading("CGGTTS Post FIT");
+
+ let cmd = if cfg!(not(feature = "cggtts")) {
+ cmd.arg(
+ Arg::new("cggtts")
+ .long("cggtts")
+ .action(ArgAction::SetTrue)
+ .help("[NOT AVAILABLE] requires cggtts compilation option"),
+ )
+ } else {
+ cmd
+ .arg(Arg::new("cggtts")
+ .long("cggtts")
+ .action(ArgAction::SetTrue)
+ .help("Activate CGGTTS special Post FIT"))
+ .arg(Arg::new("tracking")
+ .long("trk")
+ .value_parser(value_parser!(Duration))
+ .action(ArgAction::Set)
+ .help("CGGTTS custom tracking duration.
+ Otherwise, the default tracking duration is used. Refer to [https://docs.rs/cggtts/latest/cggtts/track/struct.Scheduler.html]."))
+ .arg(Arg::new("lab")
+ .long("lab")
+ .action(ArgAction::Set)
+ .help("Define the name of your station or laboratory here."))
+ .arg(Arg::new("utck")
+ .long("utck")
+ .action(ArgAction::Set)
+ .conflicts_with("clock")
+ .help("If the local clock tracks a local UTC replica, you can define the name
+ of this replica here."))
+ .arg(Arg::new("clock")
+ .long("clk")
+ .action(ArgAction::Set)
+ .conflicts_with("utck")
+ .help("If the local clock is not a UTC replica and has a specific name, you
+ can define it here."))
+ };
+
shared_args(cmd)
}
@@ -128,16 +157,17 @@ pub fn rtk_subcommand() -> Command {
let cmd = Command::new("rtk")
.arg_required_else_help(true)
.about(
- "Post Processed RTK. Use this mode to deploy the precise differential positioning.
-The initial context describes the Rover context. rtk accepts `-f` and `-d` once again, to describe the remote Station.
-Other positioning flags still apply (like -c). See --help.",
+ "Post-processed RTK (differential navigation).
+The initial context describes the rover context.
+Use rtk --fp,-f to describe the base station, with at least one RINEX file (mandatory).
+The RINEX file must describe the station position.
+RINEX-Cli is limited to static base! moving base is not feasible.
+The RTK solutions are added to the final report, as an extra chapter. See --help.",
)
.long_about(
"RTK post opmode resolves PVT solutions by (post processed) differential navigation.
The initial context (-f, -d) describes the ROVER.
-`rtk` also accepts -f and -d and you need to use those to describe the BASE (mandatory).
-Other than that, `rtk` is stricly identical to `ppp` and is presented similarly.
-CGGTTS and other options still apply."
+`rtk` also accepts -f and -d and you need to use those to describe the base station (mandatory).",
)
.arg(
Arg::new("fp")
@@ -145,7 +175,7 @@ CGGTTS and other options still apply."
.value_name("FILE")
.action(ArgAction::Append)
.required_unless_present("dir")
- .help("Pass any RINEX file for remote base station"),
+ .help("Base station Observation RINEX file(s), one at a time, as many as needed."),
)
.arg(
Arg::new("dir")
@@ -153,7 +183,9 @@ CGGTTS and other options still apply."
.value_name("DIR")
.action(ArgAction::Append)
.required_unless_present("fp")
- .help("Pass any directory for remote base station"),
+ .help(
+ "Base station Observation RINEX directory, one at a time, as many as needed.",
+ ),
);
shared_args(cmd)
}
diff --git a/src/fops/filegen.rs b/src/fops/filegen.rs
index 9164df2..41cf38d 100644
--- a/src/fops/filegen.rs
+++ b/src/fops/filegen.rs
@@ -31,13 +31,11 @@ pub fn filegen(ctx: &Context, matches: &ArgMatches, submatches: &ArgMatches) ->
fn write(ctx: &Context, matches: &ArgMatches, submatches: &ArgMatches) -> Result<(), Error> {
let ctx_data = &ctx.data;
for (product, dir) in [
- (ProductType::DORIS, "DORIS"),
(ProductType::Observation, "OBSERVATIONS"),
(ProductType::MeteoObservation, "METEO"),
(ProductType::BroadcastNavigation, "BRDC"),
(ProductType::HighPrecisionClock, "CLOCK"),
(ProductType::HighPrecisionOrbit, "SP3"),
- (ProductType::IONEX, "IONEX"),
(ProductType::ANTEX, "ANTEX"),
] {
if let Some(rinex) = ctx_data.rinex(product) {
diff --git a/src/fops/merge.rs b/src/fops/merge.rs
index 8320191..8b91745 100644
--- a/src/fops/merge.rs
+++ b/src/fops/merge.rs
@@ -64,14 +64,6 @@ pub fn merge(ctx: &Context, cli: &Cli, submatches: &ArgMatches) -> Result<(), Er
rinex_a.merge(&rinex_b)?,
)
},
- RinexType::IonosphereMaps => {
- let rinex_a = ctx_data.ionex().ok_or(Error::MissingIONEX)?;
-
- (
- rinex_a.standard_filename(short_v2_name, None, None),
- rinex_a.merge(&rinex_b)?,
- )
- },
RinexType::ClockData => {
let rinex_a = ctx_data.clock().ok_or(Error::MissingClockRinex)?;
diff --git a/src/fops/split.rs b/src/fops/split.rs
index f42e62c..b032efe 100644
--- a/src/fops/split.rs
+++ b/src/fops/split.rs
@@ -21,7 +21,6 @@ pub fn split(ctx: &Context, submatches: &ArgMatches) -> Result<(), Error> {
ProductType::MeteoObservation,
ProductType::BroadcastNavigation,
ProductType::HighPrecisionClock,
- ProductType::IONEX,
] {
if let Some(rinex) = ctx_data.rinex(product) {
let (rinex_a, rinex_b) = rinex.split(*split_instant);
diff --git a/src/fops/tbin.rs b/src/fops/tbin.rs
index 3bdd004..672a33b 100644
--- a/src/fops/tbin.rs
+++ b/src/fops/tbin.rs
@@ -28,8 +28,6 @@ pub fn time_binning(
ctx.workspace.create_subdir("BATCH");
for product in [
- ProductType::IONEX,
- ProductType::DORIS,
ProductType::Observation,
ProductType::MeteoObservation,
ProductType::BroadcastNavigation,
diff --git a/src/main.rs b/src/main.rs
index b3e1bbb..8e178e2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -73,7 +73,6 @@ fn user_data_parsing(
single_files: Vec<&String>,
directories: Vec<&String>,
max_depth: usize,
- is_rover: bool,
) -> QcContext {
let mut ctx = QcContext::new();
@@ -178,18 +177,7 @@ fn user_data_parsing(
// Preprocessing
context_preprocessing(&mut ctx, cli);
- match cli.matches.subcommand() {
- Some(("rtk", _)) => {
- if is_rover {
- debug!("ROVER Dataset: {:?}", ctx);
- } else {
- error!("BASE STATION Dataset: {:?}", ctx);
- }
- },
- _ => {
- debug!("{:?}", ctx);
- },
- }
+ debug!("{:?}", ctx);
ctx
}
@@ -216,7 +204,6 @@ pub fn main() -> Result<(), Error> {
cli.rover_files(),
cli.rover_directories(),
max_recursive_depth,
- true,
);
let ctx_stem = Context::context_stem(&mut data_ctx);
@@ -316,36 +303,44 @@ pub fn main() -> Result<(), Error> {
fops::filegen(&ctx, &cli.matches, submatches)?;
return Ok(());
},
+
Some(("merge", submatches)) => {
fops::merge(&ctx, &cli, submatches)?;
return Ok(());
},
+
Some(("split", submatches)) => {
fops::split(&ctx, submatches)?;
return Ok(());
},
+
Some(("tbin", submatches)) => {
fops::time_binning(&ctx, &cli.matches, submatches)?;
return Ok(());
},
+
Some(("cbin", submatches)) => {
fops::constell_timescale_binning(&ctx, submatches)?;
return Ok(());
},
+
Some(("diff", submatches)) => {
fops::diff(&ctx, &cli, submatches)?;
return Ok(());
},
+
#[cfg(feature = "ppp")]
Some(("ppp", submatches)) => {
- let chapter = positioning::precise_positioning(&cli, &ctx, false, submatches)?;
+ let chapter = positioning::precise_positioning(&ctx, false, submatches)?;
extra_pages.push(chapter);
},
+
#[cfg(feature = "ppp")]
Some(("rtk", submatches)) => {
- let chapter = positioning::precise_positioning(&cli, &ctx, true, submatches)?;
+ let chapter = positioning::precise_positioning(&ctx, true, submatches)?;
extra_pages.push(chapter);
},
+
_ => {},
}
diff --git a/src/positioning/biases/environment.rs b/src/positioning/biases/environment.rs
new file mode 100644
index 0000000..72f165b
--- /dev/null
+++ b/src/positioning/biases/environment.rs
@@ -0,0 +1,142 @@
+use log::info;
+
+use gnss_rtk::prelude::{BiasRuntime, EnvironmentalBias, TroposphereModel};
+
+pub struct EnvironmentalBiases {}
+
+impl EnvironmentalBias for EnvironmentalBiases {
+ fn ionosphere_bias_m(&self, _: &BiasRuntime) -> f64 {
+ // TODO
+ 0.0
+ }
+
+ fn troposphere_bias_m(&self, rtm: &BiasRuntime) -> f64 {
+ TroposphereModel::Niel.bias_m(rtm)
+ }
+}
+
+//pub fn tropo_components(meteo: Option<&Rinex>, t: Epoch, lat_ddeg: f64) -> Option<(f64, f64)> {
+// const MAX_LATDDEG_DELTA: f64 = 15.0;
+// let max_dt = Duration::from_hours(24.0);
+// let rnx = meteo?;
+// let meteo = rnx.header.meteo.as_ref().unwrap();
+//
+// let delays: Vec<(Observable, f64)> = meteo
+// .sensors
+// .iter()
+// .filter_map(|s| match s.observable {
+// Observable::ZenithDryDelay => {
+// let (x, y, z, _) = s.position?;
+// let (lat, _, _) = ecef2geodetic(x, y, z, Ellipsoid::WGS84);
+// let lat = rad2deg(lat);
+// if (lat - lat_ddeg).abs() < MAX_LATDDEG_DELTA {
+// let value = rnx
+// .zenith_dry_delay()
+// .filter(|(t_sens, _)| (*t_sens - t).abs() < max_dt)
+// .min_by_key(|(t_sens, _)| (*t_sens - t).abs());
+// let (_, value) = value?;
+// debug!("{:?} lat={} zdd {}", t, lat_ddeg, value);
+// Some((s.observable.clone(), value))
+// } else {
+// None
+// }
+// },
+// Observable::ZenithWetDelay => {
+// let (x, y, z, _) = s.position?;
+// let (mut lat, _, _) = ecef2geodetic(x, y, z, Ellipsoid::WGS84);
+// lat = rad2deg(lat);
+// if (lat - lat_ddeg).abs() < MAX_LATDDEG_DELTA {
+// let value = rnx
+// .zenith_wet_delay()
+// .filter(|(t_sens, _)| (*t_sens - t).abs() < max_dt)
+// .min_by_key(|(t_sens, _)| (*t_sens - t).abs());
+// let (_, value) = value?;
+// debug!("{:?} lat={} zdd {}", t, lat_ddeg, value);
+// Some((s.observable.clone(), value))
+// } else {
+// None
+// }
+// },
+// _ => None,
+// })
+// .collect();
+//
+// if delays.len() < 2 {
+// None
+// } else {
+// let zdd = delays
+// .iter()
+// .filter_map(|(obs, value)| {
+// if obs == &Observable::ZenithDryDelay {
+// Some(*value)
+// } else {
+// None
+// }
+// })
+// .reduce(|k, _| k)
+// .unwrap();
+//
+// let zwd = delays
+// .iter()
+// .filter_map(|(obs, value)| {
+// if obs == &Observable::ZenithWetDelay {
+// Some(*value)
+// } else {
+// None
+// }
+// })
+// .reduce(|k, _| k)
+// .unwrap();
+//
+// Some((zwd, zdd))
+// }
+//}
+
+// /*
+// * Grabs nearest BD model (in time)
+// */
+// pub fn bd_model(nav: &Rinex, t: Epoch) -> Option {
+// let (_, model) = nav
+// .nav_bdgim_models_iter()
+// .min_by_key(|(k_i, _)| (k_i.epoch - t).abs())?;
+
+// Some(BdModel { alpha: model.alpha })
+// }
+
+// /*
+// * Grabs nearest NG model (in time)
+// */
+// pub fn ng_model(nav: &Rinex, t: Epoch) -> Option {
+// let (_, model) = nav
+// .nav_nequickg_models_iter()
+// .min_by_key(|(k_i, _)| (k_i.epoch - t).abs())?;
+
+// Some(NgModel { a: model.a })
+// }
+
+// /// Returns a [KbModel]
+// pub fn kb_model(nav: &Rinex, t: Epoch) -> Option {
+// let (nav_key, model) = nav
+// .nav_klobuchar_models_iter()
+// .min_by_key(|(k_i, _)| (k_i.epoch - t).abs())?;
+//
+// Some(KbModel {
+// h_km: {
+// match nav_key.sv.constellation {
+// Constellation::BeiDou => 375.0,
+// // we only expect GPS or BDS here,
+// // badly formed RINEX will generate errors in the solutions
+// _ => 350.0,
+// }
+// },
+// alpha: model.alpha,
+// beta: model.beta,
+// })
+// }
+
+impl EnvironmentalBiases {
+ pub fn new() -> Self {
+ info!("environemental biases model created & deployed");
+ Self {}
+ }
+}
diff --git a/src/positioning/biases/mod.rs b/src/positioning/biases/mod.rs
new file mode 100644
index 0000000..3ee58ca
--- /dev/null
+++ b/src/positioning/biases/mod.rs
@@ -0,0 +1,2 @@
+pub mod environment;
+pub mod space;
diff --git a/src/positioning/biases/space.rs b/src/positioning/biases/space.rs
new file mode 100644
index 0000000..6c3614a
--- /dev/null
+++ b/src/positioning/biases/space.rs
@@ -0,0 +1,53 @@
+use log::info;
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use gnss_rtk::prelude::{BiasRuntime, Duration, Epoch, SatelliteClockCorrection, SpacebornBias};
+
+use crate::positioning::EphemerisBuffer;
+
+pub struct SpacebornBiases<'a> {
+ buffer: Rc>>,
+}
+
+impl<'a> SpacebornBias for SpacebornBiases<'a> {
+ fn clock_bias(&self, rtm: &BiasRuntime) -> SatelliteClockCorrection {
+ if let Some(frm) = self.buffer.borrow().select_sv_ephemeris(rtm.epoch, rtm.sv) {
+ if let Some(dt) = frm
+ .ephemeris
+ .clock_correction(frm.toc, rtm.epoch, rtm.sv, 10)
+ {
+ SatelliteClockCorrection::without_relativistic_correction(dt)
+ } else {
+ Default::default()
+ }
+ } else {
+ Default::default()
+ }
+ }
+
+ fn group_delay(&self, rtm: &BiasRuntime) -> Duration {
+ if let Some(frame) = self.buffer.borrow().select_sv_ephemeris(rtm.epoch, rtm.sv) {
+ if let Some(tgd) = frame.ephemeris.tgd() {
+ tgd
+ } else {
+ Duration::ZERO
+ }
+ } else {
+ Duration::ZERO
+ }
+ }
+
+ fn mw_bias(&self, _: &BiasRuntime) -> f64 {
+ // MW bias not supplied nor supported yet
+ Default::default()
+ }
+}
+
+impl<'a> SpacebornBiases<'a> {
+ pub fn new(buffer: Rc>>) -> Self {
+ info!("spaceborn biases created & deployed");
+ Self { buffer }
+ }
+}
diff --git a/src/positioning/cggtts/mod.rs b/src/positioning/cggtts/mod.rs
index 91527ed..1d92920 100644
--- a/src/positioning/cggtts/mod.rs
+++ b/src/positioning/cggtts/mod.rs
@@ -1,5 +1,8 @@
//! CGGTTS special resolution opmode.
-use std::{cell::RefCell, collections::HashMap};
+use std::collections::HashMap;
+
+use std::cell::{RefCell, RefMut};
+use std::rc::Rc;
mod post_process;
pub use post_process::post_process;
@@ -13,8 +16,9 @@ use rinex::{
};
use gnss_rtk::prelude::{
- AbsoluteTime, Bias, Candidate, Carrier as RTKCarrier, Method, Observation, OrbitSource, User,
- PPP, SPEED_OF_LIGHT_M_S,
+ AbsoluteTime, Candidate, Carrier as RTKCarrier, ClockProfile, EnvironmentalBias,
+ EphemerisSource, Method, Observation, OrbitSource, Solver, SpacebornBias, UserParameters,
+ UserProfile, SPEED_OF_LIGHT_M_S,
};
use cggtts::prelude::{
@@ -23,9 +27,7 @@ use cggtts::prelude::{
use crate::{
cli::Context,
- positioning::{
- cast_rtk_carrier, ClockStateProvider, EphemerisSource, Error as PositioningError,
- },
+ positioning::{cast_rtk_carrier, EphemerisBuffer, Error as PositioningError},
};
fn rinex_ref_observable(
@@ -82,13 +84,19 @@ fn rinex_ref_observable(
}
/// Resolves CGGTTS tracks from input context
-pub fn resolve<'a, 'b, CK: ClockStateProvider, O: OrbitSource, B: Bias, T: AbsoluteTime>(
+pub fn resolve<
+ 'a,
+ EPH: EphemerisSource,
+ ORB: OrbitSource,
+ EB: EnvironmentalBias,
+ SB: SpacebornBias,
+ TIM: AbsoluteTime,
+>(
ctx: &Context,
- eph: &'a RefCell>,
- user_profile: User,
- mut clock: CK,
- mut solver: PPP,
method: Method,
+ params: UserParameters,
+ mut solver: Solver,
+ ephemeris_buffer: Rc>>,
) -> Result, PositioningError> {
let obs_data = ctx
.data
@@ -128,27 +136,15 @@ pub fn resolve<'a, 'b, CK: ClockStateProvider, O: OrbitSource, B: Bias, T: Absol
let mut release = false;
- for (index, (t, signal)) in obs_data.signal_observations_sampling_ok_iter().enumerate() {
- if index > 0 && t > past_t {
+ for (index, (epoch, signal)) in obs_data.signal_observations_sampling_ok_iter().enumerate() {
+ ephemeris_buffer.borrow_mut().new_epoch(epoch);
+
+ if index > 0 && epoch > past_t {
if collecting {
info!("{} - new epoch", past_t);
for (sv, observations) in sv_observations.iter() {
// create new candidate
let mut cd = Candidate::new(*sv, past_t, observations.clone());
-
- // fixup and customizations
- match clock.next_clock_at(past_t, *sv) {
- Some(dt) => cd.set_clock_correction(dt),
- None => error!("{} ({}) - no clock correction available", past_t, *sv),
- }
-
- if let Some((_, _, eph)) = eph.borrow_mut().select(past_t, *sv) {
- if let Some(tgd) = eph.tgd() {
- debug!("{} ({}) - tgd: {}", past_t, *sv, tgd);
- cd.set_group_delay(tgd);
- }
- }
-
candidates.push(cd);
}
@@ -164,7 +160,7 @@ pub fn resolve<'a, 'b, CK: ClockStateProvider, O: OrbitSource, B: Bias, T: Absol
}
}
- match solver.resolve(user_profile, past_t, &candidates) {
+ match solver.ppp(past_t, params, &candidates) {
Ok(pvt) => {
for sv_contrib in pvt.sv.iter() {
let (azim_deg, elev_deg) =
@@ -247,11 +243,12 @@ pub fn resolve<'a, 'b, CK: ClockStateProvider, O: OrbitSource, B: Bias, T: Absol
if carrier.is_err() {
error!(
"{}({}/{}) - unknown signal {:?}",
- t,
+ epoch,
signal.sv.constellation,
signal.observable,
carrier.err().unwrap()
);
+
continue;
}
@@ -262,11 +259,12 @@ pub fn resolve<'a, 'b, CK: ClockStateProvider, O: OrbitSource, B: Bias, T: Absol
if rtk_carrier.is_err() {
error!(
"{}({}/{}) - unknown frequency: {}",
- t,
+ epoch,
signal.sv.constellation,
signal.observable,
rtk_carrier.err().unwrap()
);
+
continue;
}
@@ -345,9 +343,9 @@ pub fn resolve<'a, 'b, CK: ClockStateProvider, O: OrbitSource, B: Bias, T: Absol
}
// update only on new epochs
- if index > 0 && t > past_t {
+ if index > 0 && epoch > past_t {
if collecting {
- if t > next_period_start {
+ if epoch > next_period_start {
release = true;
// end of period: release attempt
for ((sv, sv_ref), tracker) in trackers.iter_mut() {
@@ -375,19 +373,31 @@ pub fn resolve<'a, 'b, CK: ClockStateProvider, O: OrbitSource, B: Bias, T: Absol
}
}
} else {
- info!("{} - {} until CGGTTS release", t, next_period_start - t);
+ info!(
+ "{} - {} until CGGTTS release",
+ epoch,
+ next_period_start - epoch
+ );
}
} else {
// not collecting
- if t >= next_collection_start {
- next_period_start = cv_calendar.next_period_start_after(t);
+ if epoch >= next_collection_start {
+ next_period_start = cv_calendar.next_period_start_after(epoch);
collecting = true;
- debug!("{} - CGGTTS tracking started", t);
- info!("{} - {} until CGGTTS release", t, next_period_start - t);
+ debug!("{} - CGGTTS tracking started", epoch);
+ info!(
+ "{} - {} until CGGTTS release",
+ epoch,
+ next_period_start - epoch
+ );
}
if !collecting {
- debug!("{} - {} until next tracking", t, next_collection_start - t);
+ debug!(
+ "{} - {} until next tracking",
+ epoch,
+ next_collection_start - epoch
+ );
}
}
}
@@ -395,20 +405,28 @@ pub fn resolve<'a, 'b, CK: ClockStateProvider, O: OrbitSource, B: Bias, T: Absol
if release {
// reset
release = false;
- next_period_start = cv_calendar.next_period_start_after(t);
+ next_period_start = cv_calendar.next_period_start_after(epoch);
next_collection_start = cv_calendar.next_data_collection_after(past_t);
- collecting = t > next_collection_start;
+ collecting = epoch > next_collection_start;
if collecting {
- debug!("{} - CGGTTS tracking started", t);
+ debug!("{} - CGGTTS tracking started", epoch);
} else {
- debug!("{} - {} until next tracking", t, next_collection_start - t);
+ debug!(
+ "{} - {} until next tracking",
+ epoch,
+ next_collection_start - epoch
+ );
}
- info!("{} - {} until CGGTTS release", t, next_period_start - t);
+ info!(
+ "{} - {} until CGGTTS release",
+ epoch,
+ next_period_start - epoch
+ );
}
- past_t = t;
+ past_t = epoch;
}
Ok(tracks)
diff --git a/src/positioning/clock.rs b/src/positioning/clock.rs
index ead25f5..c566fb9 100644
--- a/src/positioning/clock.rs
+++ b/src/positioning/clock.rs
@@ -5,11 +5,7 @@ use crate::{
use std::{cell::RefCell, collections::HashMap};
-use gnss_rtk::prelude::{ClockCorrection, Duration, Epoch, SV};
-
-pub trait ClockStateProvider {
- fn next_clock_at(&mut self, t: Epoch, sv: SV) -> Option;
-}
+use gnss_rtk::prelude::{Duration, Epoch, SatelliteClockCorrection, SV};
impl CenteredDataPoints for f64 {
fn zero() -> f64 {
diff --git a/src/positioning/eph.rs b/src/positioning/eph.rs
deleted file mode 100644
index 7aafaf6..0000000
--- a/src/positioning/eph.rs
+++ /dev/null
@@ -1,106 +0,0 @@
-use crate::cli::Context;
-use rinex::navigation::Ephemeris;
-use rinex::prelude::{Epoch, SV};
-use std::collections::HashMap;
-
-pub struct EphemerisSource<'a> {
- sv: SV,
- eos: bool,
- toc: Epoch,
- buffer: HashMap>,
- iter: Box + 'a>,
-}
-
-impl<'a> EphemerisSource<'a> {
- /// Builds new [EphemerisSource] from [Context]
- pub fn from_ctx(ctx: &'a Context) -> Self {
- let brdc = ctx
- .data
- .brdc_navigation()
- .expect("Navigation RINEX is currently mandatory..");
-
- info!("Ephemeris data source created.");
-
- let mut s = Self {
- eos: false,
- sv: SV::default(),
- toc: Epoch::default(),
- buffer: HashMap::with_capacity(32),
- iter: Box::new(brdc.nav_ephemeris_frames_iter().filter_map(|(k, v)| {
- let toe = v.toe(k.sv)?;
- Some((k.sv, k.epoch, toe, v))
- })),
- };
-
- s.consume_many(32); // fill in with some data
- s
- }
-
- /// Consume one entry from [Iterator]
- fn consume_one(&mut self) {
- if let Some((sv, toc, toe, eph)) = self.iter.next() {
- if let Some(buffer) = self.buffer.get_mut(&sv) {
- buffer.push((toc, toe, eph.clone()));
- } else {
- self.buffer.insert(sv, vec![(toc, toe, eph.clone())]);
- }
- self.sv = sv;
- self.toc = toc;
- } else {
- if !self.eos {
- info!("{}({}): consumed all epochs", self.toc, self.sv);
- }
- self.eos = true;
- }
- }
-
- /// Consume n entries from [Iterator]
- fn consume_many(&mut self, n: usize) {
- for _ in 0..n {
- self.consume_one();
- }
- }
-
- /// [Ephemeris] selection attempt, for [SV] at [Epoch]
- fn try_select(&self, t: Epoch, sv: SV) -> Option<(Epoch, Epoch, &Ephemeris)> {
- let buffer = self.buffer.get(&sv)?;
-
- if sv.constellation.is_sbas() {
- buffer
- .iter()
- .filter_map(|(toc_i, toe_i, eph_i)| {
- if t >= *toc_i {
- Some((*toc_i, *toe_i, eph_i))
- } else {
- None
- }
- })
- .min_by_key(|(toc_i, _, _)| (t - *toc_i).abs())
- } else {
- buffer
- .iter()
- .filter_map(|(toc_i, toe_i, eph_i)| {
- if eph_i.is_valid(sv, t) {
- Some((*toc_i, *toe_i, eph_i))
- } else {
- None
- }
- })
- .min_by_key(|(_, toe_i, _)| (t - *toe_i).abs())
- }
- }
-
- /// [Ephemeris] selection at [Epoch] for [SV].
- pub fn select(&mut self, t: Epoch, sv: SV) -> Option<(Epoch, Epoch, Ephemeris)> {
- loop {
- if let Some((toc_i, toe_i, eph_i)) = self.try_select(t, sv) {
- return Some((toc_i, toe_i, eph_i.clone()));
- } else {
- self.consume_one();
- if self.eos {
- return None;
- }
- }
- }
- }
-}
diff --git a/src/positioning/ephemeris.rs b/src/positioning/ephemeris.rs
new file mode 100644
index 0000000..4c5dee0
--- /dev/null
+++ b/src/positioning/ephemeris.rs
@@ -0,0 +1,122 @@
+use gnss_rtk::prelude::{Ephemeris as RawEphemeris, EphemerisSource, Epoch, SV};
+
+use rinex::navigation::Ephemeris;
+
+use crate::cli::Context;
+
+pub struct NullEphemerisSource {}
+
+impl NullEphemerisSource {
+ pub fn new() -> Self {
+ Self {}
+ }
+}
+
+impl EphemerisSource for NullEphemerisSource {
+ // Ephemeris interface is not used by this application
+ fn ephemeris_data(&self, _: Epoch, _: SV) -> Option {
+ None
+ }
+}
+
+#[derive(Clone)]
+pub struct EphemerisFrame {
+ /// [SV]
+ pub sv: SV,
+
+ /// ToC as [Epoch]
+ pub toc: Epoch,
+
+ /// ToE as [Epoch]
+ pub toe: Epoch,
+
+ /// [Ephemeris]
+ pub ephemeris: Ephemeris,
+}
+
+pub struct EphemerisBuffer<'a> {
+ /// End of ephemeris Stream
+ eos: bool,
+
+ /// buffer
+ buffer: Vec,
+
+ /// Iterator
+ iter: Box + 'a>,
+}
+
+impl<'a> EphemerisBuffer<'a> {
+ pub fn new_epoch(&mut self, epoch: Epoch) {
+ // discard past invalid
+ self.buffer.retain(|frm| {
+ if frm.toe < epoch {
+ frm.ephemeris.is_valid(frm.sv, epoch)
+ } else {
+ true
+ }
+ });
+
+ // iterate until EoS or frame becomes future + invalid
+ loop {
+ if self.eos {
+ break;
+ }
+
+ if let Some(frame) = self.iter.next() {
+ if frame.toe > epoch && !frame.ephemeris.is_valid(frame.sv, epoch) {
+ self.buffer.push(frame);
+ break;
+ }
+
+ self.buffer.push(frame);
+ } else {
+ self.eos = true;
+ }
+ }
+ }
+
+ pub fn new(ctx: &'a Context) -> Self {
+ let brdc = ctx
+ .data
+ .brdc_navigation()
+ .expect("Navigation RINEX is required by post-processed navigation");
+
+ let s = Self {
+ eos: false,
+ buffer: Vec::with_capacity(16),
+ iter: Box::new(brdc.nav_ephemeris_frames_iter().filter_map(|(k, v)| {
+ if let Some(toe) = v.toe(k.sv) {
+ Some(EphemerisFrame {
+ toe,
+ sv: k.sv,
+ toc: k.epoch,
+ ephemeris: v.clone(),
+ })
+ } else {
+ error!(
+ "{}({}) - ephemeris error (non supported constellation?)",
+ k.epoch, k.sv
+ );
+ None
+ }
+ })),
+ };
+
+ info!("Ephemeris buffer created");
+
+ s
+ }
+
+ pub fn select_sv_ephemeris(&self, epoch: Epoch, sv: SV) -> Option {
+ if sv.constellation.is_sbas() {
+ error!("{} - sbas not supported yet!", epoch);
+ None
+ } else {
+ self.buffer
+ .iter()
+ .filter(|frm| frm.sv == sv && frm.ephemeris.is_valid(sv, epoch))
+ .min_by_key(|frm| (epoch - frm.toe).abs())
+ .cloned()
+ }
+ }
+}
diff --git a/src/positioning/mod.rs b/src/positioning/mod.rs
index a098ddb..4d01083 100644
--- a/src/positioning/mod.rs
+++ b/src/positioning/mod.rs
@@ -1,73 +1,57 @@
-use crate::cli::{Cli, Context};
-use clap::ArgMatches;
-use std::cell::RefCell;
-use std::fs::read_to_string;
-
mod buffer;
-pub use buffer::Buffer;
-
+mod coords;
+mod orbit;
+mod precise;
mod snapshot;
-pub use snapshot::{CenteredDataPoints, CenteredSnapshot};
+mod time;
+// mod clock;
+mod biases;
+mod ephemeris;
+mod ppp; // precise point positioning
+mod rtk; // RTK positioning
-mod eph;
-use eph::EphemerisSource;
+#[cfg(feature = "cggtts")]
+mod cggtts; // CGGTTS special solver
-mod precise;
-use precise::PreciseOrbits;
+pub use buffer::Buffer;
+pub use coords::Coords3d;
+pub use snapshot::{CenteredDataPoints, CenteredSnapshot};
-mod time;
+use orbit::Orbits;
+use precise::PreciseOrbits;
use time::Time;
-mod ppp; // precise point positioning
+use biases::{environment::EnvironmentalBiases, space::SpacebornBiases};
+
+use ephemeris::{EphemerisBuffer, NullEphemerisSource};
+
use ppp::{
post_process::{post_process as ppp_post_process, Error as PPPPostError},
Report as PPPReport,
};
-#[cfg(feature = "cggtts")]
-mod cggtts; // CGGTTS special solver
+use rtk::RTKBaseStation;
#[cfg(feature = "cggtts")]
use cggtts::{post_process as cggtts_post_process, Report as CggttsReport};
-// mod rtk;
-// pub use rtk::RemoteRTKReference;
+use log::error;
-mod orbit;
-use orbit::Orbits;
-
-mod coords;
-pub use coords::Coords3d;
-
-mod clock;
-use clock::Clock;
-pub use clock::ClockStateProvider;
-
-use rinex::{
- carrier::Carrier,
- prelude::{Constellation, Rinex},
-};
+use rinex::{carrier::Carrier, prelude::Rinex};
use gnss_qc::prelude::QcExtraPage;
use gnss_rtk::prelude::{
- Bias, BiasRuntime, Carrier as RTKCarrier, Config, Duration, Epoch, Error as RTKError, KbModel,
- Method, Profile, TroposphereModel, User, PPP,
+ Carrier as RTKCarrier, ClockProfile, Config, Duration, Error as RTKError, Method, Rc, Solver,
+ UserParameters, UserProfile,
};
use thiserror::Error;
-struct BiasModel {}
-
-impl Bias for BiasModel {
- fn ionosphere_bias_m(&self, _: &BiasRuntime) -> f64 {
- 0.0
- }
-
- fn troposphere_bias_m(&self, rtm: &BiasRuntime) -> f64 {
- TroposphereModel::Niel.bias_m(rtm)
- }
-}
+use crate::cli::{Cli, Context};
+use clap::ArgMatches;
+use std::cell::RefCell;
+use std::fs::read_to_string;
#[derive(Debug, Error)]
pub enum Error {
@@ -88,158 +72,32 @@ pub fn rtk_carrier_cast(carrier: RTKCarrier) -> Carrier {
RTKCarrier::B3 => Carrier::B3,
RTKCarrier::E5a5b => Carrier::E5a5b,
RTKCarrier::L1 => Carrier::L1,
- RTKCarrier::G1 => Carrier::G1(None),
- RTKCarrier::G2 => Carrier::G2(None),
- RTKCarrier::G3 => Carrier::G3,
RTKCarrier::E5b => Carrier::E5b,
- RTKCarrier::E6Lex => Carrier::E6,
- RTKCarrier::G1a => Carrier::G1a,
- RTKCarrier::G2a => Carrier::G2a,
RTKCarrier::L5 => Carrier::L5,
RTKCarrier::L2 => Carrier::L2,
- RTKCarrier::S => Carrier::S,
}
}
/// Converts [Carrier] to [RTKCarrier]
pub fn cast_rtk_carrier(carrier: Carrier) -> Result {
- let freq_mhz = carrier.frequency_mega_hz();
- RTKCarrier::from_frequency_mega_hz(freq_mhz)
-}
-
-// // helper in reference signal determination
-// fn rtk_reference_carrier(carrier: RTKCarrier) -> bool {
-// matches!(
-// carrier,
-// RTKCarrier::L1 | RTKCarrier::E1 | RTKCarrier::B1c | RTKCarrier::B1i
-// )
-// }
-
-//use map_3d::{ecef2geodetic, rad2deg, Ellipsoid};
-
-//pub fn tropo_components(meteo: Option<&Rinex>, t: Epoch, lat_ddeg: f64) -> Option<(f64, f64)> {
-// const MAX_LATDDEG_DELTA: f64 = 15.0;
-// let max_dt = Duration::from_hours(24.0);
-// let rnx = meteo?;
-// let meteo = rnx.header.meteo.as_ref().unwrap();
-//
-// let delays: Vec<(Observable, f64)> = meteo
-// .sensors
-// .iter()
-// .filter_map(|s| match s.observable {
-// Observable::ZenithDryDelay => {
-// let (x, y, z, _) = s.position?;
-// let (lat, _, _) = ecef2geodetic(x, y, z, Ellipsoid::WGS84);
-// let lat = rad2deg(lat);
-// if (lat - lat_ddeg).abs() < MAX_LATDDEG_DELTA {
-// let value = rnx
-// .zenith_dry_delay()
-// .filter(|(t_sens, _)| (*t_sens - t).abs() < max_dt)
-// .min_by_key(|(t_sens, _)| (*t_sens - t).abs());
-// let (_, value) = value?;
-// debug!("{:?} lat={} zdd {}", t, lat_ddeg, value);
-// Some((s.observable.clone(), value))
-// } else {
-// None
-// }
-// },
-// Observable::ZenithWetDelay => {
-// let (x, y, z, _) = s.position?;
-// let (mut lat, _, _) = ecef2geodetic(x, y, z, Ellipsoid::WGS84);
-// lat = rad2deg(lat);
-// if (lat - lat_ddeg).abs() < MAX_LATDDEG_DELTA {
-// let value = rnx
-// .zenith_wet_delay()
-// .filter(|(t_sens, _)| (*t_sens - t).abs() < max_dt)
-// .min_by_key(|(t_sens, _)| (*t_sens - t).abs());
-// let (_, value) = value?;
-// debug!("{:?} lat={} zdd {}", t, lat_ddeg, value);
-// Some((s.observable.clone(), value))
-// } else {
-// None
-// }
-// },
-// _ => None,
-// })
-// .collect();
-//
-// if delays.len() < 2 {
-// None
-// } else {
-// let zdd = delays
-// .iter()
-// .filter_map(|(obs, value)| {
-// if obs == &Observable::ZenithDryDelay {
-// Some(*value)
-// } else {
-// None
-// }
-// })
-// .reduce(|k, _| k)
-// .unwrap();
-//
-// let zwd = delays
-// .iter()
-// .filter_map(|(obs, value)| {
-// if obs == &Observable::ZenithWetDelay {
-// Some(*value)
-// } else {
-// None
-// }
-// })
-// .reduce(|k, _| k)
-// .unwrap();
-//
-// Some((zwd, zdd))
-// }
-//}
-
-/// Returns a [KbModel]
-pub fn kb_model(nav: &Rinex, t: Epoch) -> Option {
- let (nav_key, model) = nav
- .nav_klobuchar_models_iter()
- .min_by_key(|(k_i, _)| (k_i.epoch - t).abs())?;
-
- Some(KbModel {
- h_km: {
- match nav_key.sv.constellation {
- Constellation::BeiDou => 375.0,
- // we only expect GPS or BDS here,
- // badly formed RINEX will generate errors in the solutions
- _ => 350.0,
- }
+ match carrier {
+ Carrier::B1 => Ok(RTKCarrier::B1),
+ Carrier::B3 => Ok(RTKCarrier::B3),
+ Carrier::E5a5b => Ok(RTKCarrier::E5a5b),
+ Carrier::L2 => Ok(RTKCarrier::L2),
+ Carrier::L5 | Carrier::E5a => Ok(RTKCarrier::L5),
+ Carrier::L1 | Carrier::E1 => Ok(RTKCarrier::L1),
+ Carrier::E5b => Ok(RTKCarrier::E5b),
+ carrier => {
+ error!("{} - signal not supported", carrier);
+ Err(RTKError::UnknownCarrierFrequency)
},
- alpha: model.alpha,
- beta: model.beta,
- })
+ }
}
-// /*
-// * Grabs nearest BD model (in time)
-// */
-// pub fn bd_model(nav: &Rinex, t: Epoch) -> Option {
-// let (_, model) = nav
-// .nav_bdgim_models_iter()
-// .min_by_key(|(k_i, _)| (k_i.epoch - t).abs())?;
-
-// Some(BdModel { alpha: model.alpha })
-// }
-
-// /*
-// * Grabs nearest NG model (in time)
-// */
-// pub fn ng_model(nav: &Rinex, t: Epoch) -> Option {
-// let (_, model) = nav
-// .nav_nequickg_models_iter()
-// .min_by_key(|(k_i, _)| (k_i.epoch - t).abs())?;
-
-// Some(NgModel { a: model.a })
-// }
-
pub fn precise_positioning(
- _cli: &Cli,
ctx: &Context,
- is_rtk: bool,
+ uses_rtk: bool,
matches: &ArgMatches,
) -> Result {
// Load custom configuration script, or Default
@@ -251,31 +109,10 @@ pub fn precise_positioning(
let cfg: Config = serde_json::from_str(&content)
.unwrap_or_else(|e| panic!("failed to parse configuration: {}", e));
- /*
- * CGGTTS special case
- */
- #[cfg(not(feature = "cggtts"))]
- if matches.get_flag("cggtts") {
- panic!("--cggtts option not available: compile with cggtts option");
- }
-
- info!("Using custom solver configuration: {:#?}", cfg);
cfg
},
None => {
- let method = Method::default();
-
- let cfg = Config::default().with_navigation_method(method);
-
- /*
- * CGGTTS special case
- */
- #[cfg(not(feature = "cggtts"))]
- if matches.get_flag("cggtts") {
- panic!("--cggtts option not available: compile with cggtts option");
- }
-
- info!("Using {:?} default preset: {:#?}", method, cfg);
+ let cfg = Config::default();
cfg
},
};
@@ -286,13 +123,21 @@ pub fn precise_positioning(
"Positioning requires Observation RINEX"
);
- if !is_rtk {
- assert!(
- ctx.data.brdc_navigation().is_some(),
- "Positioning requires Navigation RINEX"
- );
+ assert!(
+ ctx.data.brdc_navigation().is_some(),
+ "Positioning requires Navigation RINEX"
+ );
+
+ /*
+ * CGGTTS special case
+ */
+ #[cfg(not(feature = "cggtts"))]
+ if matches.get_flag("cggtts") {
+ panic!("--cggtts option not available: compile with cggtts option");
}
+ info!("Using custom solver configuration: {:#?}", cfg);
+
if let Some(obs_rinex) = ctx.data.observation() {
if let Some(obs_header) = &obs_rinex.header.obs {
if let Some(time_of_first_obs) = obs_header.timeof_first_obs {
@@ -323,17 +168,30 @@ pub fn precise_positioning(
}
}
- // print config to be used
- info!("Using {:?} method", cfg.method);
+ let rtk_obs = if uses_rtk {
+ rtk::parse_rinex(&matches)
+ } else {
+ Rinex::basic_obs()
+ };
- // create data providers
- let eph = RefCell::new(EphemerisSource::from_ctx(ctx));
+ // Deploy base station if needed
+ let base_station = if uses_rtk {
+ Some(RTKBaseStation::new(&rtk_obs))
+ } else {
+ None
+ };
- let clocks = Clock::new(&ctx, &eph);
let time = Time::new(&ctx);
- let orbits = Orbits::new(&ctx, &eph);
- // let mut rtk_reference = RemoteRTKReference::from_ctx(&ctx);
+ let ephemeris_buffer = EphemerisBuffer::new(&ctx);
+ let ephemeris_buffer = Rc::new(RefCell::new(ephemeris_buffer));
+
+ let env_biases = EnvironmentalBiases::new();
+ let orbits = Orbits::new(&ctx, Rc::clone(&ephemeris_buffer));
+ let space_biases = SpacebornBiases::new(Rc::clone(&ephemeris_buffer));
+
+ // Ephemeris interface is not used by this application
+ let null_eph = NullEphemerisSource::new();
// reference point is mandatory to CGGTTS opmode
#[cfg(feature = "cggtts")]
@@ -346,8 +204,6 @@ If your dataset does not describe one, you can manually describe one, see --help
}
}
- let bias_model = BiasModel {};
-
let apriori = ctx.rx_orbit;
let apriori_ecef_m = match apriori {
@@ -358,35 +214,53 @@ If your dataset does not describe one, you can manually describe one, see --help
None => None,
};
- let solver = PPP::new(
+ let solver = Solver::new(
ctx.data.almanac.clone(),
ctx.data.earth_cef,
cfg.clone(),
+ null_eph.into(),
orbits.into(),
+ space_biases.into(),
+ env_biases.into(),
time,
- bias_model,
apriori_ecef_m,
);
- let user_profile = User {
- profile: {
- if matches.get_flag("static") {
- Profile::Static
- } else {
- Profile::Pedestrian
- }
- },
- clock_sigma_s: if let Some(clock_sigma) = matches.get_one::("clock-sigma") {
- *clock_sigma
- } else {
- 0.01
- },
+ let user_profile = if matches.get_flag("static") {
+ UserProfile::Static
+ } else if matches.get_flag("car") {
+ UserProfile::Car
+ } else if matches.get_flag("airplane") {
+ UserProfile::Airplane
+ } else if matches.get_flag("rocket") {
+ UserProfile::Rocket
+ } else {
+ UserProfile::Pedestrian
+ };
+
+ let clock_profile = if matches.get_flag("quartz") {
+ ClockProfile::Quartz
+ } else if matches.get_flag("atomic") {
+ ClockProfile::Atomic
+ } else if matches.get_flag("h-maser") {
+ ClockProfile::HydrogenMaser
+ } else {
+ ClockProfile::Oscillator
};
+ info!(
+ "deployed with {} profile - clock profile: {:?}",
+ user_profile, clock_profile
+ );
+
+ let user_params = UserParameters::new(user_profile.clone(), clock_profile.clone());
+
+ // PPP+CGGTTS special case
#[cfg(feature = "cggtts")]
if matches.get_flag("cggtts") {
//* CGGTTS special opmode */
- let tracks = cggtts::resolve(ctx, &eph, user_profile, clocks, solver, cfg.method)?;
+ let tracks = cggtts::resolve(ctx, cfg.method, user_params, solver, ephemeris_buffer)?;
+
if !tracks.is_empty() {
cggtts_post_process(&ctx, &tracks, matches)?;
let report = CggttsReport::new(&ctx, &tracks);
@@ -398,11 +272,12 @@ If your dataset does not describe one, you can manually describe one, see --help
}
}
- /* PPP */
- let solutions = ppp::resolve(ctx, &eph, user_profile, clocks, solver);
+ // PPP/RTK
+ let solutions = ppp::resolve(ctx, user_params, solver, ephemeris_buffer);
+
if !solutions.is_empty() {
ppp_post_process(&ctx, &solutions, matches)?;
- let report = PPPReport::new(&cfg, &ctx, user_profile, &solutions);
+ let report = PPPReport::new(&cfg, &ctx, user_profile, clock_profile, &solutions);
Ok(report.formalize())
} else {
error!("solver did not generate a single solution");
diff --git a/src/positioning/orbit.rs b/src/positioning/orbit.rs
index e40a8c2..8fd1984 100644
--- a/src/positioning/orbit.rs
+++ b/src/positioning/orbit.rs
@@ -1,73 +1,85 @@
use crate::{
cli::Context,
- positioning::{Buffer, CenteredSnapshot, Coords3d, EphemerisSource, PreciseOrbits},
+ positioning::{Buffer, CenteredSnapshot, Coords3d, EphemerisBuffer, PreciseOrbits},
};
use anise::errors::AlmanacError;
use rinex::carrier::Carrier;
use gnss_rtk::prelude::{
- Almanac, Duration, Epoch, Frame, Orbit, OrbitSource, Vector3, EARTH_J2000, SUN_J2000, SV,
+ Almanac, Duration, Epoch, Frame, Orbit, OrbitSource, Rc, Vector3, EARTH_J2000, SUN_J2000, SV,
};
use std::{cell::RefCell, collections::HashMap};
-pub struct Orbits<'a, 'b> {
- eos: bool,
+pub struct Orbits<'a> {
+ // eos_precise: bool,
has_precise: bool,
- eph: &'a RefCell>,
- precise: RefCell>,
+ // has_precise: bool,
+ // eph: &'a RefCell>,
+ // precise: RefCell>,
+ ephemeris_buffer: Rc>>,
}
-impl<'a, 'b> Orbits<'a, 'b> {
- pub fn new(ctx: &'a Context, eph: &'a RefCell>) -> Self {
- let has_precise = ctx.data.has_sp3();
- let precise = RefCell::new(PreciseOrbits::new(ctx));
-
+impl<'a> Orbits<'a> {
+ pub fn new(ctx: &'a Context, ephemeris_buffer: Rc>>) -> Self {
+ // let has_precise = ctx.data.has_sp3();
+ // let precise = RefCell::new(PreciseOrbits::new(ctx));
Self {
- eph,
- precise,
- eos: false,
- has_precise,
+ ephemeris_buffer,
+ has_precise: false,
}
}
}
-impl OrbitSource for Orbits<'_, '_> {
- fn next_at(&self, t: Epoch, sv: SV, frame: Frame) -> Option {
+impl OrbitSource for Orbits<'_> {
+ fn state_at(&self, epoch: Epoch, sv: SV, frame: Frame) -> Option {
if self.has_precise {
- let mut precise_orbits = self.precise.borrow_mut();
- let orbit = precise_orbits.next_precise_at(t, sv, frame)?;
- let state = orbit.to_cartesian_pos_vel();
-
- let (x_km, y_km, z_km) = (state[0], state[1], state[2]);
+ panic!("not yet");
+ } else {
+ if let Some(frm) = self
+ .ephemeris_buffer
+ .borrow()
+ .select_sv_ephemeris(epoch, sv)
+ {
+ if let Some(orbit) = frm.ephemeris.kepler2position(sv, epoch) {
+ let pos_vel_km = orbit.to_cartesian_pos_vel();
+ debug!(
+ "{}({}) - kepler state: x={}km y={}km z={}km",
+ epoch.round(Duration::from_milliseconds(1.0)),
+ sv,
+ pos_vel_km[0],
+ pos_vel_km[1],
+ pos_vel_km[2],
+ );
- debug!(
- "{} ({}) - precise state : x={}, y={}, z={} (km, ECEF)",
- t.round(Duration::from_milliseconds(1.0)),
- sv,
- x_km,
- y_km,
- z_km
- );
+ Some(orbit)
+ } else {
+ error!("{}({}) - kepler solver failed", epoch, sv);
+ None
+ }
+ } else {
+ error!("{}({}) - no ephemeris available", epoch, sv);
+ None
+ }
+ }
+ // if self.has_precise {
+ // let mut precise_orbits = self.precise.borrow_mut();
+ // let orbit = precise_orbits.next_precise_at(t, sv, frame)?;
+ // let state = orbit.to_cartesian_pos_vel();
- Some(orbit)
- } else {
- let (toc, _, eph) = self.eph.borrow_mut().select(t, sv)?;
- let orbit = eph.kepler2position(sv, t)?;
- let state = orbit.to_cartesian_pos_vel();
- let (x_km, y_km, z_km) = (state[0], state[1], state[2]);
+ // let (x_km, y_km, z_km) = (state[0], state[1], state[2]);
- debug!(
- "{} ({}) - keplerian state : x={}, y={}, z={} (km, ECEF)",
- t.round(Duration::from_milliseconds(1.0)),
- sv,
- x_km,
- y_km,
- z_km
- );
+ // debug!(
+ // "{} ({}) - precise state : x={}, y={}, z={} (km, ECEF)",
+ // t.round(Duration::from_milliseconds(1.0)),
+ // sv,
+ // x_km,
+ // y_km,
+ // z_km
+ // );
- Some(orbit)
- }
+ // Some(orbit)
+ // } else {
}
}
diff --git a/src/positioning/ppp/mod.rs b/src/positioning/ppp/mod.rs
index f4987cc..daec7c0 100644
--- a/src/positioning/ppp/mod.rs
+++ b/src/positioning/ppp/mod.rs
@@ -1,12 +1,13 @@
//! PPP solver
use crate::{
cli::Context,
- positioning::{cast_rtk_carrier, ClockStateProvider, EphemerisSource},
+ positioning::{cast_rtk_carrier, EphemerisBuffer},
};
use std::{
- cell::RefCell,
+ cell::{RefCell, RefMut},
collections::{BTreeMap, HashMap},
+ rc::Rc,
};
use rinex::{
@@ -20,15 +21,22 @@ pub use report::Report;
pub mod post_process;
use gnss_rtk::prelude::{
- AbsoluteTime, Bias, Candidate, Epoch, Observation, OrbitSource, PVTSolution, User, PPP,
+ AbsoluteTime, Candidate, ClockProfile, EnvironmentalBias, EphemerisSource, Epoch, Observation,
+ OrbitSource, PVTSolution, Solver, SpacebornBias, UserParameters, UserProfile,
};
-pub fn resolve<'a, 'b, CK: ClockStateProvider, O: OrbitSource, B: Bias, T: AbsoluteTime>(
+pub fn resolve<
+ 'a,
+ EPH: EphemerisSource,
+ ORB: OrbitSource,
+ EB: EnvironmentalBias,
+ SB: SpacebornBias,
+ TIM: AbsoluteTime,
+>(
ctx: &Context,
- eph: &'a RefCell>,
- user_profile: User,
- mut clock: CK,
- mut solver: PPP,
+ user_params: UserParameters,
+ mut solver: Solver,
+ ephemeris_buffer: Rc>>,
) -> BTreeMap {
let mut past_epoch = Option::::None;
@@ -40,20 +48,20 @@ pub fn resolve<'a, 'b, CK: ClockStateProvider, O: OrbitSource, B: Bias, T: Absol
let mut candidates = Vec::::with_capacity(4);
let mut sv_observations = HashMap::>::new();
- // TODO: RTK
- let mut remote_observations = Vec::::new();
+ for (epoch, signal) in obs_data.signal_observations_sampling_ok_iter() {
+ ephemeris_buffer.borrow_mut().new_epoch(epoch);
- for (t, signal) in obs_data.signal_observations_sampling_ok_iter() {
let carrier = Carrier::from_observable(signal.sv.constellation, &signal.observable);
if carrier.is_err() {
error!(
"{}({}/{}) - unknown signal {:?}",
- t,
+ epoch,
signal.sv.constellation,
signal.observable,
carrier.err().unwrap()
);
+
continue;
}
@@ -64,53 +72,40 @@ pub fn resolve<'a, 'b, CK: ClockStateProvider, O: OrbitSource, B: Bias, T: Absol
if rtk_carrier.is_err() {
error!(
"{}({}/{}) - unknown frequency: {}",
- t,
+ epoch,
signal.sv.constellation,
signal.observable,
rtk_carrier.err().unwrap()
);
+
continue;
}
let rtk_carrier = rtk_carrier.unwrap();
if let Some(past_t) = past_epoch {
- if t > past_t {
+ if epoch > past_t {
// New epoch: solving attempt
for (sv, observations) in sv_observations.iter() {
// Create new candidate
let mut cd = Candidate::new(*sv, past_t, observations.clone());
-
- // candidate "fixup" or customizations
- match clock.next_clock_at(past_t, *sv) {
- Some(dt) => cd.set_clock_correction(dt),
- None => error!("{} ({}) - no clock correction available", past_t, *sv),
- }
-
- if let Some((_, _, eph)) = eph.borrow_mut().select(past_t, *sv) {
- if let Some(tgd) = eph.tgd() {
- debug!("{} ({}) - tgd: {}", past_t, *sv, tgd);
- cd.set_group_delay(tgd);
- }
- }
-
candidates.push(cd);
}
- match solver.resolve(user_profile, past_t, &candidates) {
+ match solver.ppp(past_t, user_params, &candidates) {
Ok(pvt) => {
info!(
- "{} : new pvt solution {:?} dt={}",
- pvt.epoch, pvt.pos_m, pvt.clock_offset_s
+ "{} : new {:?} solution {:?} dt={}",
+ pvt.epoch, pvt.solution_type, pvt.pos_m, pvt.clock_offset_s
);
+
solutions.insert(pvt.epoch, pvt);
},
- Err(e) => warn!("{} : pvt solver error \"{}\"", past_t, e),
+ Err(e) => warn!("{} : solver error \"{}\"", past_t, e),
}
candidates.clear();
sv_observations.clear();
- remote_observations.clear();
}
}
@@ -185,8 +180,7 @@ pub fn resolve<'a, 'b, CK: ClockStateProvider, O: OrbitSource, B: Bias, T: Absol
_ => {},
}
}
-
- past_epoch = Some(t);
+ past_epoch = Some(epoch);
}
solutions
}
diff --git a/src/positioning/ppp/post_process.rs b/src/positioning/ppp/post_process.rs
index 2f7b4df..cd0ce62 100644
--- a/src/positioning/ppp/post_process.rs
+++ b/src/positioning/ppp/post_process.rs
@@ -61,7 +61,7 @@ pub fn post_process(
for (epoch, solution) in solutions {
let (x_m, y_m, z_m) = solution.pos_m;
- let (vel_x_ms, vel_y_ms, vel_z_ms) = solution.vel_m_s;
+ let (vel_x_ms, vel_y_ms, vel_z_ms) = (0.0, 0.0, 0.0); //solution.vel_m_s;
let (lat_deg, long_deg, alt_m) = solution.lat_long_alt_deg_deg_m;
let (lat_rad, long_rad) = (lat_deg.to_radians(), long_deg.to_radians());
diff --git a/src/positioning/ppp/report.rs b/src/positioning/ppp/report.rs
index a43f9b3..44054f7 100644
--- a/src/positioning/ppp/report.rs
+++ b/src/positioning/ppp/report.rs
@@ -2,8 +2,8 @@ use crate::cli::Context;
use std::collections::BTreeMap;
use gnss_rtk::prelude::{
- Config as NaviConfig, Duration, Epoch, Method as NaviMethod, PVTSolution,
- Profile as NaviProfile, TimeScale, User as UserProfile, SV,
+ ClockProfile, Config as NaviConfig, Duration, Epoch, Method as NaviMethod, PVTSolution,
+ TimeScale, UserParameters, UserProfile, SV,
};
use gnss_qc::{
@@ -30,13 +30,14 @@ impl Render for ReportTab {
struct Summary {
method: NaviMethod,
- profile: NaviProfile,
orbit: String,
first_epoch: Epoch,
last_epoch: Epoch,
duration: Duration,
satellites: Vec,
timescale: TimeScale,
+ user_profile: UserProfile,
+ clock_profile: ClockProfile,
final_err_m: Option<(f64, f64, f64)>,
final_geo_ddeg_m: (f64, f64, f64),
averaged_err_m: Option<(f64, f64, f64)>,
@@ -52,10 +53,27 @@ impl Render for Summary {
tbody {
tr {
th class="is-info" {
- "Profile"
+ "Session"
}
- td {
- (self.profile.to_string())
+ @ if self.user_profile == UserProfile::Static {
+ td {
+ ("Static")
+ }
+ } @ else {
+ td {
+ ("Kinematic")
+ }
+ }
+ }
+ tr {
+ th class="is-info" {
+ "User Profile"
+ }
+ }
+
+ tr {
+ th class="is-info" {
+ "Clock Profile"
}
}
@@ -296,7 +314,8 @@ impl Summary {
fn new(
cfg: &NaviConfig,
ctx: &Context,
- user: &UserProfile,
+ user_profile: UserProfile,
+ clock_profile: ClockProfile,
solutions: &BTreeMap,
x0_y0_z0_m: Option<(f64, f64, f64)>,
lat0_long0_alt0_ddeg_ddeg_km: Option<(f64, f64, f64)>,
@@ -350,7 +369,7 @@ impl Summary {
}
}
- if user.profile == NaviProfile::Static {
+ if user_profile == UserProfile::Static {
// averaged coords
if let Some(averaged_latlongalt_ddeg_m) = &mut averaged_latlongalt_ddeg_m {
*averaged_latlongalt_ddeg_m = (
@@ -388,6 +407,8 @@ impl Summary {
last_epoch,
timescale,
satellites,
+ user_profile,
+ clock_profile,
final_err_m,
final_geo_ddeg_m,
averaged_err_m,
@@ -400,8 +421,6 @@ impl Summary {
}
},
method: cfg.method,
- profile: user.profile,
- // filter: cfg.solver.filter,
duration: last_epoch - first_epoch,
surveyed_lat_long_alt_ddeg_ddeg_km: lat0_long0_alt0_ddeg_ddeg_km,
}
@@ -411,32 +430,45 @@ impl Summary {
struct ReportContent {
/// summary
summary: Summary,
+
/// sv_plot
sv_plot: Plot,
+
/// map_proj
map_proj: Plot,
+
/// clk_plot
clk_plot: Plot,
+
/// drift_plot
drift_plot: Plot,
+
/// ddeg_plot
ddeg_plot: Plot,
+
/// altitude_plot
altitude_plot: Plot,
+
/// coords_err
coords_err_plot: Option,
+
/// 3d_plot
coords_err3d_plot: Option,
+
/// velocity_plot
vel_plot: Plot,
+
/// DOP
dop_plot: Plot,
+
/// TDOP
tdop_plot: Plot,
+
// /// NAVI
// navi_plot: Plot,
/// tropod
tropod_plot: Plot,
+
/// ionod
ionod_plot: Plot,
}
@@ -446,6 +478,7 @@ impl ReportContent {
cfg: &NaviConfig,
ctx: &Context,
user_profile: UserProfile,
+ clock_profile: ClockProfile,
solutions: &BTreeMap,
) -> Self {
let nb_solutions = solutions.len();
@@ -470,7 +503,8 @@ impl ReportContent {
let summary = Summary::new(
cfg,
ctx,
- &user_profile,
+ user_profile.clone(),
+ clock_profile,
solutions,
x0y0z0_m,
lat0_long0_alt0_km,
@@ -517,7 +551,7 @@ impl ReportContent {
for (index, (_, sol_i)) in solutions.iter().enumerate() {
let (lat_ddeg, long_ddeg, _) = sol_i.lat_long_alt_deg_deg_m;
- let modulo = if user_profile.profile == NaviProfile::Static {
+ let modulo = if user_profile == UserProfile::Static {
10
} else {
1
@@ -645,53 +679,53 @@ impl ReportContent {
let mut plot =
Plot::timedomain_plot("vel_plot", "Velocity", "Velocity [m/s]", true);
- let vel_x = solutions
- .iter()
- .map(|(_, sol)| sol.vel_m_s.0)
- .collect::>();
-
- let vel_y = solutions
- .iter()
- .map(|(_, sol)| sol.vel_m_s.1)
- .collect::>();
-
- let vel_z = solutions
- .iter()
- .map(|(_, sol)| sol.vel_m_s.2)
- .collect::>();
-
- let trace = Plot::timedomain_chart(
- "vel_x",
- Mode::Markers,
- MarkerSymbol::Cross,
- &epochs,
- vel_x,
- true,
- );
-
- plot.add_trace(trace);
-
- let trace = Plot::timedomain_chart(
- "vel_y",
- Mode::Markers,
- MarkerSymbol::Cross,
- &epochs,
- vel_y,
- true,
- );
-
- plot.add_trace(trace);
-
- let trace = Plot::timedomain_chart(
- "vel_z",
- Mode::Markers,
- MarkerSymbol::Cross,
- &epochs,
- vel_z,
- true,
- );
-
- plot.add_trace(trace);
+ // let vel_x = solutions
+ // .iter()
+ // .map(|(_, sol)| sol.vel_m_s.0)
+ // .collect::>();
+
+ // let vel_y = solutions
+ // .iter()
+ // .map(|(_, sol)| sol.vel_m_s.1)
+ // .collect::>();
+
+ // let vel_z = solutions
+ // .iter()
+ // .map(|(_, sol)| sol.vel_m_s.2)
+ // .collect::>();
+
+ // let trace = Plot::timedomain_chart(
+ // "vel_x",
+ // Mode::Markers,
+ // MarkerSymbol::Cross,
+ // &epochs,
+ // vel_x,
+ // true,
+ // );
+
+ // plot.add_trace(trace);
+
+ // let trace = Plot::timedomain_chart(
+ // "vel_y",
+ // Mode::Markers,
+ // MarkerSymbol::Cross,
+ // &epochs,
+ // vel_y,
+ // true,
+ // );
+
+ // plot.add_trace(trace);
+
+ // let trace = Plot::timedomain_chart(
+ // "vel_z",
+ // Mode::Markers,
+ // MarkerSymbol::Cross,
+ // &epochs,
+ // vel_z,
+ // true,
+ // );
+
+ // plot.add_trace(trace);
plot
},
tropod_plot: {
@@ -884,20 +918,20 @@ impl ReportContent {
let mut plot =
Plot::timedomain_plot("clk_drift", "Clock Drift", "Drift [s/s]", true);
- let clock_drift = solutions
- .iter()
- .map(|(_, sol)| sol.clock_drift_s_s)
- .collect::>();
-
- let trace = Plot::timedomain_chart(
- "drift",
- Mode::Markers,
- MarkerSymbol::Cross,
- &epochs,
- clock_drift,
- true,
- );
- plot.add_trace(trace);
+ // let clock_drift = solutions
+ // .iter()
+ // .map(|(_, sol)| sol.clock_drift_s_s)
+ // .collect::>();
+
+ // let trace = Plot::timedomain_chart(
+ // "drift",
+ // Mode::Markers,
+ // MarkerSymbol::Cross,
+ // &epochs,
+ // clock_drift,
+ // true,
+ // );
+ // plot.add_trace(trace);
plot
},
@@ -1172,11 +1206,12 @@ impl Report {
cfg: &NaviConfig,
ctx: &Context,
user_profile: UserProfile,
+ clock_profile: ClockProfile,
solutions: &BTreeMap,
) -> Self {
Self {
tab: ReportTab {},
- content: ReportContent::new(cfg, ctx, user_profile, solutions),
+ content: ReportContent::new(cfg, ctx, user_profile, clock_profile, solutions),
}
}
}
diff --git a/src/positioning/rtk.rs b/src/positioning/rtk.rs
index 6a8379a..6241feb 100644
--- a/src/positioning/rtk.rs
+++ b/src/positioning/rtk.rs
@@ -1,99 +1,111 @@
-use crate::{cli::Context, positioning::cast_rtk_carrier};
+use log::info;
-use gnss_rtk::prelude::{Epoch, Observation as RTKObservation, SV};
+use crate::{cli::Context, positioning::cast_rtk_carrier};
-use rinex::{
- prelude::{Carrier, Observable, obs::EpochFlag},
+use rinex::prelude::{
+ obs::{EpochFlag, SignalObservation},
+ Carrier, Epoch, Observable, Rinex, RinexType,
};
+use clap::ArgMatches;
+
+use gnss_rtk::prelude::{Candidate, RTKBase};
+
use std::collections::{BTreeMap, HashMap};
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-struct BufKey {
- pub t: Epoch,
- pub sv: SV,
- pub carrier: Carrier,
+pub fn parse_rinex(matches: &ArgMatches) -> Rinex {
+ let mut first_file = Option::::None;
+
+ let file_path = matches
+ .get_one::("fp")
+ .expect("Base station observations required!");
+
+ let rinex = if file_path.ends_with(".gz") {
+ Rinex::from_gzip_file(file_path)
+ } else {
+ Rinex::from_file(file_path)
+ };
+
+ let mut rinex = rinex.unwrap_or_else(|e| {
+ panic!("Failed to parse base station observations: {}", e);
+ });
+
+ rinex
}
-pub struct RemoteRTKReference<'a> {
- buffer: HashMap,
- iter: Box<
- dyn Iterator<
- Item = (
- &'a (Epoch, EpochFlag),
- &'a (
- Option,
- BTreeMap>,
- ),
- ),
- > + 'a,
- >,
+pub struct RTKBaseStation<'a> {
+ /// Name of this station
+ name: String,
+
+ /// Reference position is mandatory
+ reference_position_ecef_m: (f64, f64, f64),
+
+ /// End of observations stream
+ eos: bool,
+
+ /// Iterator
+ iter: Box + 'a>,
+
+ /// Buffer
+ buffer: Vec<(Epoch, SignalObservation)>,
}
-impl<'a> RemoteRTKReference<'a> {
- pub fn from_ctx(ctx: &'a Context) -> Self {
- if let Some(reference_site) = ctx.reference_site.as_ref() {
- if let Some(remote_obs) = reference_site.data.observation() {
- info!("Remote reference site context created");
- Self {
- iter: remote_obs.observation(),
- buffer: HashMap::with_capacity(16),
+impl<'a> RTKBase for RTKBaseStation<'a> {
+ fn name(&self) -> String {
+ self.name.clone()
+ }
+
+ fn new_epoch(&mut self, epoch: Epoch) {
+ // consume till EoS or new epoch
+ loop {
+ if self.eos {
+ break;
+ }
+
+ if let Some((epoch, observation)) = self.iter.next() {
+ self.buffer.push((epoch, observation.clone()));
+
+ if epoch > epoch {
+ break;
}
} else {
- warn!("Not RTK compatible: missing remote observations");
- Self {
- iter: Box::new([].into_iter()),
- buffer: HashMap::with_capacity(16),
- }
- }
- } else {
- warn!("Not RTK compatible: no reference site definition");
- Self {
- iter: Box::new([].into_iter()),
- buffer: HashMap::with_capacity(16),
+ self.eos = true;
}
}
}
- pub fn observe(&mut self, t: Epoch, sv: SV, carrier: Carrier) -> Option {
- let rtk_carrier = cast_rtk_carrier(carrier);
- let mut ret = Option::::None;
- let key = BufKey { t, sv, carrier };
- if let Some(value) = self.buffer.get(&key) {
- // TODO (SNR)
- ret = Some(RTKObservation::pseudo_range(rtk_carrier, *value, None));
- }
- if ret.is_none() {
- while let Some(((remote_t, remote_flag), (_, remote_svnn))) = self.iter.next() {
- if remote_flag.is_ok() {
- for (remote_sv, remote_obsnn) in remote_svnn.iter() {
- for (remote_observable, remote_obs) in remote_obsnn.iter() {
- if let Ok(remote_carrier) =
- Carrier::from_observable(remote_sv.constellation, remote_observable)
- {
- let key = BufKey {
- t: *remote_t,
- sv: *remote_sv,
- carrier: remote_carrier,
- };
- let remote_rtk_carrier = cast_rtk_carrier(remote_carrier);
- if *remote_t == t && *remote_sv == sv && remote_carrier == carrier {
- // TODO (SNR)
- return Some(RTKObservation::pseudo_range(
- remote_rtk_carrier,
- remote_obs.obs,
- None,
- ));
- } else {
- self.buffer.insert(key, remote_obs.obs);
- }
- }
- }
- }
- }
- }
- }
- self.buffer.retain(|k, _| k.t >= t);
- ret
+ fn observe(&self, epoch: Epoch) -> Vec {
+ vec![]
+ }
+
+ fn reference_position_ecef_m(&self, _: Epoch) -> (f64, f64, f64) {
+ self.reference_position_ecef_m
+ }
+}
+
+impl<'a> RTKBaseStation<'a> {
+ pub fn new(rinex: &'a Rinex) -> Self {
+ assert_eq!(
+ rinex.header.rinex_type,
+ RinexType::ObservationData,
+ "base station file must be Observation RINEX!"
+ );
+
+ let reference_position_ecef_m = rinex
+ .header
+ .rx_position
+ .as_ref()
+ .expect("base station coordinates must be descrined in the RINEX header!");
+
+ let s = Self {
+ eos: false,
+ name: "Base".to_string(),
+ buffer: Vec::with_capacity(8),
+ iter: rinex.signal_observations_sampling_ok_iter(),
+ reference_position_ecef_m: *reference_position_ecef_m,
+ };
+
+ info!("{} - rtk base station deployed", s.name());
+ s
}
}
diff --git a/src/positioning/time.rs b/src/positioning/time.rs
index 1e36c27..8bca815 100644
--- a/src/positioning/time.rs
+++ b/src/positioning/time.rs
@@ -1,8 +1,8 @@
-use crate::cli::Context;
use gnss_qc::prelude::TimeCorrectionsDB;
-use hifitime::Unit;
-
use gnss_rtk::prelude::{AbsoluteTime, Epoch, TimeScale};
+use log::info;
+
+use crate::cli::Context;
pub struct Time {
database: Option,
@@ -34,6 +34,7 @@ impl AbsoluteTime for Time {
impl Time {
pub fn new(ctx: &Context) -> Self {
+ info!("time corrections database created");
Self {
database: ctx.data.time_corrections_database(),
}