Skip to content

Commit

Permalink
Develop (#172)
Browse files Browse the repository at this point in the history
* Rinex.navigation() should be available by default
* Remove deprecated method.

User is expected to use Rinex.snr() iterator.

* Remove deprecated comments.

User is expected to user the preprocessing toolkit.

* improve and fix Merge operation

fn merge_mut() was moved into a dedicated trait.
Improved and fixed is_merged()

* Observation SNR/lli

- add possibility to compare SNR to value expressed in dB
- add documentation

* improving ionex definitions

* ionex plotting
* improving sbas support
* sbas vehicles identification
* improving the test infra
* rtk opmode
* rtk solver now works in mixed GPS, GAL, BDS

---------

Signed-off-by: Guillaume W. Bres <[email protected]>
  • Loading branch information
gwbres authored Oct 8, 2023
1 parent 8ae76a8 commit 1be46bc
Show file tree
Hide file tree
Showing 124 changed files with 6,410 additions and 4,415 deletions.
65 changes: 61 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ from an `Observable`.
- `src/hatanaka/mod.rs`: the Hatanaka module contains the RINEX Compressor and Decompressor
- `src/antex/antenna.rs`: defines the index structure of ANTEX format

NAV RINEX
=========
Navigation Data
===============

Orbit instantaneous parameters, broadcasted by GNSS vehicles, are presented in different
forms depending on the RINEX revision and the GNSS constellation.
Orbit broadcasted parameters are presented in different form depending on the RINEX revisions
and also may differ in their nature depending on which constellation we're talking about.

To solve that problem, we use a dictionary, in the form of `src/db/NAV/orbits.json`,
which describes all fields per RINEX revision and GNSS constellation.
Expand All @@ -56,3 +56,60 @@ Introducing a new RINEX type
`src/meteo/mod.rs` is the easiest format and can serve as a guideline to follow.

When introducing a new Navigation Data, the dictionary will most likely have to be updated (see previous paragraph).

GNSS Constellations
===================

Supported constellations are defined in the Constellation Module.
This structure defines both Orbiting and Stationary vehicles.

Adding new SBAS vehicles
========================

To add a newly launched SBAS vehicles, simply add it to the
rinex/db/SBAS/sbas.json database.

The only mandatory fields are :
- the "constellation" field
- the SBAS "prn" field (which is 100 + prn number)
- "id": the name of that vehicle, for example "ASTRA-5B"
- "launched\_year": the year this vehicle was launched

Other optional fields are:
- "launched\_month": month ths vehicle was launched
- "launched\_day": day of month this vehicle was launched

We don't support undeployed vehicles (in advance).

Build scripts
=============

The build script is rinex/build.rs.

It is responsible for building several important but hidden structures.

1. Navigation RINEX specs, described by rinex/db/NAV
2. Geostationary vehicles identification in rinex/db/sbas/sbas.json,
that follows the L1-CA-PRN Code assignment specifications (see online specs).
3. rinex/db/SBAS/*.wkt contains geographic definitions for most
standard SBAS systems. We parse them as Geo::LineStrings to
define a contour area for a given SBAS system. This gives one method
to select a SBAS from given location on Earth

Crate dependencies
==================

- `qc-traits` and `sinex` are core libraries.
- `rinex` is the central dependency to most other libraries or applications.
- tiny applications like `rnx2crx`, `crx2rnx` and `ublox-rnx` only depend on the rinex crate
- `sp3` is a library that only depends on `rinex`
- `gnss-rtk` is a library that depends on `rinex`, `sp3` and `rinex-qc`
- `cli` is an application that exposes `rinex-qc`, `gnss-rtk`, `sp3` and `rinex`

External key dependencies:

- `Hifitime` (timing lib) is used by all libraries
- `Nyx-space` (navigation lib) is used by `gnss-rtk`
- `Ublox-rs` (UBX protocol) is used by `ublox-rnx`

<img align="center" width="450" src="https://github.com/georust/rinex/blob/main/doc/plots/dependencies.png">
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ and we aim towards advanced geodesic and ionospheric analysis.
- Seamless .gzip decompression with `flate2` compilation feature
- RINEX V4 full support, that includes modern Navigation messages
- Meteo RINEX full support
- IONEX and Clock RINEX partial support, will be concluded soon
- IONEX (2D) support, partial 3D support
- Clock RINEX partial support: to be concluded soon
- File merging, splitting and pre processing
- Modern constellations like BeiDou, Galileo and IRNSS
- Supported time scales are GPST, BDT, GST, UTC
- Supports many SBAS, refer to online documentation
- Full support of Military codes : if you're working with such signals you can
at least run a -qc analysis, and possibly the position solver once it is merged
- Supports high precision RINEX (scaled phase data with micro cycle precision)
Expand All @@ -41,7 +43,6 @@ summon from the "cli" application directly.

- QZNSST is represented as GPST at the moment
- GLONASST and IRNSST are not supported : calculations (mostly orbits) will not be accurate
- Partial SBAS support : some features are not yet available
- The command line tool does not accept BINEX or other proprietary formats
- File production is not fully concluded to this day, some formats are still not correctly supported
(mostly NAV).
Expand Down Expand Up @@ -73,7 +74,7 @@ RINEX formats & applications
|----------------------------|-------------------|---------------------|----------------------|----------------------|--------------------------| ---------------------|
| Navigation (NAV) | :heavy_check_mark:| Ephemeris :construction: V4 :construction: | :heavy_check_mark: :chart_with_upwards_trend: | :construction: | Orbit parameters, Ionospheric models.. | Epoch iteration |
| Observation (OBS) | :heavy_check_mark:| :heavy_check_mark: | :heavy_check_mark: :chart_with_upwards_trend: | :construction: | Phase, Pseudo Range, Doppler, SSI | Epoch iteration |
| CRINEX (Compressed OBS) | :heavy_check_mark:| RNX2CRX1 :heavy_check_mark: RNX2CRX3 :construction: | :heavy_check_mark: :chart_with_upwards_trend: | :construction: | see OBS Data | Epoch iteration |
| CRINEX (Compressed OBS) | :heavy_check_mark:| RNX2CRX1 :heavy_check_mark: RNX2CRX3 :construction: | :heavy_check_mark: :chart_with_upwards_trend: | :construction: | Phase, Pseudo Range, Doppler, SSI | Epoch iteration |
| Meteorological data (MET) | :heavy_check_mark:| :heavy_check_mark: | :heavy_check_mark: :chart_with_upwards_trend: | :construction: | Meteo sensors data (Temperature, Moisture..) | Epoch iteration |
| Clocks (CLK) | :heavy_check_mark:| :construction: | :construction: |:construction: | Clock comparison | Epoch iteration |
| Antenna (ATX) | :heavy_check_mark:| :construction: | :construction: |:construction: | Antenna calibration data | Sorted by `antex::Antenna` |
Expand Down
2 changes: 1 addition & 1 deletion crx2rnx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ readme = "README.md"

[dependencies]
clap = { version = "4", features = ["derive", "color"] }
rinex = { path = "../rinex", version = "=0.14.0", features = ["serde"] }
rinex = { path = "../rinex", version = "=0.14.1", features = ["serde"] }
2 changes: 1 addition & 1 deletion crx2rnx/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl Cli {
}
}
pub fn input_path(&self) -> &str {
&self.matches.get_one::<String>("filepath").unwrap()
self.matches.get_one::<String>("filepath").unwrap()
}
pub fn output_path(&self) -> Option<&String> {
self.matches.get_one::<String>("output")
Expand Down
4 changes: 2 additions & 2 deletions crx2rnx/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ fn main() -> Result<(), rinex::Error> {
Some(path) => path.clone(),
_ => {
// deduce from input path
match input_path.strip_suffix("d") {
match input_path.strip_suffix('d') {
Some(prefix) => prefix.to_owned() + "o",
_ => match input_path.strip_suffix("D") {
_ => match input_path.strip_suffix('D') {
Some(prefix) => prefix.to_owned() + "O",
_ => match input_path.strip_suffix("crx") {
Some(prefix) => prefix.to_owned() + "rnx",
Expand Down
Binary file added doc/dependencies.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified doc/plots/sp3_residual.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/plots/tec.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions gnss-rtk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ readme = "README.md"
[dependencies]
log = "0.4"
thiserror = "1"
nalgebra = "=0.32"
nyx-space = "2.0.0-alpha.2"
rinex-qc = { path = "../rinex-qc", features = ["serde"] }
rinex = { path = "../rinex", features = ["serde", "flate2", "sbas", "obs", "nav", "qc", "processing"] }
hifitime = { version = "3.8.4", features = ["serde", "std"] }
rinex-qc = { path = "../rinex-qc", version = "=0.1.4", features = ["serde"] }
rinex = { path = "../rinex", version = "=0.14.1", features = ["serde", "flate2", "sbas", "obs", "nav", "qc", "processing"] }
serde = { version = "1.0", optional = true, default-features = false, features = ["derive"] }
100 changes: 99 additions & 1 deletion gnss-rtk/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,106 @@
GNSS-RTK
========

Precise position solver.
[![crates.io](https://img.shields.io/crates/v/gnss-rtk.svg)](https://crates.io/crates/gnss-rtk)
[![rustc](https://img.shields.io/badge/rustc-1.64%2B-blue.svg)](https://img.shields.io/badge/rustc-1.64%2B-blue.svg)
[![crates.io](https://docs.rs/gnss-rtk/badge.svg)](https://docs.rs/gnss-rtk/badge.svg)

RTK precise position and timing solver, in rust.

The Solver can work from a RINEX context blob, defined in this crate toolsuite, but is not exclusively tied to RINEX.

The solver implements precise positioning algorithms, which are based on raw GNSS signals.

Performances
============

I'm able to resolve every single Epoch in a modern 24h data context, in about 1 second, on my 8 core CPU.

Solving method
==============

Only a straightforward Matrix based resolution method is implemented.
Other solutions, like Kalman filter, exist and could potentially improve performances
at the expense of more complexity and possibly

The matrix resolution technique gives the best result for every single epoch

- there are no initialization iterations
- there is no iteration or recursive behavior

Behavior and Output
===================

The solver will try to resolve a position for every single existing Epoch.

When working with RINEX, preprocessing operations may apply.
If you're working with the attached "cli" application, this is done with `-P`.
For example, if the input context is huge, a smoothing or decimation

The solver will output a SolverEstimate object on each resolved Epoch.
Refer to this structure's documentation for more information.

Timing DOP and Position DOP are estimated and attached to every single result.

SPP
===

The solver supports the spp strategy. This strategy is the only strategy we can deploy
on single carrier context. It is most likely the unique strategy you can deploy if you're working
with old RINEX (like GPS only V2), or single frequency RINEX data.

When using SPP :

- you can only hope for residual errors of a few meters
- an interpolation order above 9 makes no sense
- Ionospheric delay must be considered and modeled. Refer to the related section.

If you're operating this library from the "cli" application integrated to this toolsuite,
a forced `--spp` mode exists. It is a convenient way to restrict this library to SPP solving
and compare it to PPP.

PPP
===

The solver will adapt to PPP strategy if the context is sufficient (more than one carrier).
PPP simplifies the solving process greatly, ionospheric delay is cancelled and does not have to be taken into account.

PPP is deployed if you're typically working with modern RINEX data.

We allow the possibility to deploy a PPP strategy without SP3 data. This is not a typical use case.
Other tools like glab or rtklib probably do not allow this.
You need to understand that in this case, you want good navigation data quality in order to reduce
the error their interpolation will introduce.

When working with PPP, we recommend the interpolation order to be set to 11 (or above).

Ionospheric Delay
=================

TODO

SP3 and Broadcast Ephemeris
===========================

The solver will always prefer SP3 over Broadcast ephemeris.
That stands whatever the solving method and strategy might be.

RTK from RINEX
==============

The solver can be initialized from a RINEX context, defined as `QcContext` in the RINEX library suite.
This structure is adaptable and quite efficient. For example it allows the combination of both
SP3 and Broadcast Ephemeris.

When initialized from RINEX, we can determine whether PPP is feasible or not

RTK Configuration
=================

The RTKConfiguration structure, describes all configuration and customization
the solver supports.

It is important to understand how, when and what to customize depending on your goals.

When working with the "cli" application, you can provide an RTKConfiguration
in the form of JSON, with `--rtk-cfg`.
131 changes: 131 additions & 0 deletions gnss-rtk/src/cfg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use crate::model::Modeling;
use crate::SolverType;
use hifitime::prelude::TimeScale;

use std::str::FromStr;

#[cfg(feature = "serde")]
use serde::Deserialize;

use rinex::prelude::GroundPosition;

use rinex::observation::Snr;

fn default_timescale() -> TimeScale {
TimeScale::GPST
}

fn default_interp() -> usize {
7
}

fn default_max_sv() -> usize {
10
}

fn default_smoothing() -> bool {
false
}

fn default_iono() -> bool {
false
}

fn default_tropo() -> bool {
false
}

#[derive(Default, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize))]
pub struct RTKConfig {
/// Time scale
#[cfg_attr(feature = "serde", serde(default = "default_timescale"))]
pub timescale: TimeScale,
/// positioning mode
#[cfg_attr(feature = "serde", serde(default))]
pub mode: SolverMode,
/// (Position) interpolation filter order.
/// A minimal order must be respected for correct results.
/// - 7 when working with broadcast ephemeris
/// - 11 when working with SP3
#[cfg_attr(feature = "serde", serde(default = "default_interp"))]
pub interp_order: usize,
/// Whether the solver is working in fixed altitude mode or not
#[cfg_attr(feature = "serde", serde(default))]
pub fixed_altitude: Option<f64>,
/// Position receveir position, if known before hand
pub rcvr_position: Option<GroundPosition>,
/// PR code smoothing filter before moving forward
#[cfg_attr(feature = "serde", serde(default = "default_smoothing"))]
pub code_smoothing: bool,
/// true if we're using troposphere modeling
#[cfg_attr(feature = "serde", serde(default = "default_tropo"))]
pub tropo: bool,
/// true if we're using ionosphere modeling
#[cfg_attr(feature = "serde", serde(default = "default_iono"))]
pub iono: bool,
/// Minimal percentage ]0; 1[ of Sun light to be received by an SV
/// for not to be considered in Eclipse.
/// A value closer to 0 means we tolerate fast Eclipse exit.
/// A value closer to 1 is a stringent criteria: eclipse must be totally exited.
#[cfg_attr(feature = "serde", serde(default))]
pub min_sv_sunlight_rate: Option<f64>,
/// Minimal elevation angle. SV below that angle will not be considered.
pub min_sv_elev: Option<f64>,
/// Minimal SNR for an SV to be considered.
pub min_sv_snr: Option<Snr>,
/// modeling
#[cfg_attr(feature = "serde", serde(default))]
pub modeling: Modeling,
/// Max. number of vehicules to consider.
/// The more the merrier, but it also means heavier computations
#[cfg_attr(feature = "serde", serde(default = "default_max_sv"))]
pub max_sv: usize,
}

impl RTKConfig {
pub fn default(solver: SolverType) -> Self {
match solver {
SolverType::SPP => Self {
timescale: default_timescale(),
mode: SolverMode::default(),
fixed_altitude: None,
rcvr_position: None,
interp_order: default_interp(),
code_smoothing: default_smoothing(),
tropo: default_tropo(),
iono: default_iono(),
min_sv_sunlight_rate: None,
min_sv_elev: Some(10.0),
min_sv_snr: Some(Snr::from_str("weak").unwrap()),
modeling: Modeling::default(),
max_sv: default_max_sv(),
},
SolverType::PPP => Self {
timescale: default_timescale(),
mode: SolverMode::default(),
fixed_altitude: None,
rcvr_position: None,
interp_order: 11,
code_smoothing: default_smoothing(),
tropo: default_tropo(),
iono: default_iono(),
min_sv_sunlight_rate: Some(0.75),
min_sv_elev: Some(25.0),
min_sv_snr: Some(Snr::from_str("strong").unwrap()),
modeling: Modeling::default(),
max_sv: default_max_sv(),
},
}
}
}

#[derive(Default, Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize))]
pub enum SolverMode {
/// Receiver is kept at fixed location
#[default]
Static,
/// Receiver is not static
Kinematic,
}
Loading

0 comments on commit 1be46bc

Please sign in to comment.