Skip to content

Commit

Permalink
Upgrade to ANISE 0.5.0 (#281)
Browse files Browse the repository at this point in the history
* increase observation precision in formatted csv
* add new DIFF example and associated data
* Upgrade ANISE 0.5
   * this upgrade fixes the .lock file problem
   * also correct the JPL model daily download
* Fix a few warnings
* add release flags at the workspace level
* update gitignore

---------

Signed-off-by: Guillaume W. Bres <[email protected]>
  • Loading branch information
gwbres authored Dec 11, 2024
1 parent 18f226f commit d0602bb
Show file tree
Hide file tree
Showing 17 changed files with 149 additions and 68 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ Cargo.lock
*.png
*.jpg
*.html
*.dhall
*.swp
*.swo
*.patch
*.txt
**/*.rs.bk
.DS_Store
*.rnx

rinex/merge.rnx
rinex/test.crx
Expand Down
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ members = [
exclude = [
"./test_resources",
]

[profile.release]
strip = true
opt-level = 3
lto = "thin"
codegen-units = 1
2 changes: 1 addition & 1 deletion rinex-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ plotly = "0.9"
# plotly = { path = "../../plotly-rs/plotly" }
# plotly = { git = "https://github.com/gwbres/plotly", branch = "scattergeo" }

anise = { version = "=0.4.2", features = ["embed_ephem"] }
anise = { version = "=0.5.0", features = ["embed_ephem"] }
hifitime = { version = "4.0.0-beta", features = ["serde", "std"] }

# gnss-rs = { version = "2.2.3", features = ["serde"] }
Expand Down
2 changes: 1 addition & 1 deletion rinex-cli/src/fops/csv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub fn write_obs_rinex<P: AsRef<Path>>(rnx: &Rinex, path: P) -> Result<(), Error
let sv = sv.to_string();
for (code, obs) in obsnn.iter() {
let code = code.to_string();
let value = format!("{:.3E}", obs.obs);
let value = format!("{:.12E}", obs.obs);
let lli = if let Some(lli) = obs.lli {
format!("{:?}", lli)
} else {
Expand Down
2 changes: 1 addition & 1 deletion rinex-qc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ thiserror = "1"
strum = "0.26"
itertools = "0.13.0"
strum_macros = "0.26"
anise = { version = "=0.4.2", features = ["embed_ephem"] }
anise = { version = "0.5.0", features = ["embed_ephem"] }
serde = { version = "1.0", default-features = false, features = ["derive"] }

statrs = { version = "0.16", optional = true }
Expand Down
135 changes: 86 additions & 49 deletions rinex-qc/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
//! GNSS processing context definition.
use thiserror::Error;

use std::collections::HashMap;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::{
collections::HashMap,
ffi::OsStr,
fs::create_dir_all,
fs::File,
io::Write,
path::{Path, PathBuf},
};

use rinex::{
merge::{Error as RinexMergeError, Merge as RinexMerge},
Expand All @@ -13,7 +18,11 @@ use rinex::{
};

use anise::{
almanac::{metaload::MetaFile, planetary::PlanetaryDataError},
almanac::{
metaload::MetaAlmanacError,
metaload::{MetaAlmanac, MetaFile},
planetary::PlanetaryDataError,
},
constants::frames::{EARTH_ITRF93, IAU_EARTH_FRAME},
errors::AlmanacError,
prelude::Frame,
Expand All @@ -32,6 +41,8 @@ use qc_traits::{
pub enum Error {
#[error("almanac error")]
Alamanac(#[from] AlmanacError),
#[error("almanac setup error")]
MetaAlamanac(#[from] MetaAlmanacError),
#[error("planetary data error")]
PlanetaryData(#[from] PlanetaryDataError),
#[error("failed to extend gnss context")]
Expand Down Expand Up @@ -158,88 +169,112 @@ pub struct QcContext {
}

impl QcContext {
const ALMANAC_LOCAL_STRORAGE: &str = ".cache";

fn nyx_anise_de440s_bsp() -> MetaFile {
MetaFile {
crc32: Some(1921414410),
uri: String::from("http://public-data.nyxspace.com/anise/de440s.bsp"),
}
}

fn nyx_anise_pck11_pca() -> MetaFile {
MetaFile {
crc32: Some(0x8213b6e9),
uri: String::from("http://public-data.nyxspace.com/anise/v0.4/pck11.pca"),
uri: String::from("http://public-data.nyxspace.com/anise/v0.5/pck11.pca"),
}
}
fn jpl_latest_high_prec_bpc() -> MetaFile {

fn nyx_anise_jpl_bpc() -> MetaFile {
MetaFile {
crc32: None,
uri:
"https://naif.jpl.nasa.gov/pub/naif/generic_kernels/pck/earth_latest_high_prec.bpc"
.to_string(),
}
}
/// Infaillible method to either download, retrieve or create

/// Method to either download, retrieve or create
/// a basic [Almanac] and reference [Frame] to work with.
/// We always prefer the highest precision scenario.
/// On first deployment, it will require internet access.
/// We can only rely on lower precision kernels if we cannot access the cloud.
fn build_almanac() -> Result<(Almanac, Frame), Error> {
let almanac = Almanac::until_2035()?;
match almanac.load_from_metafile(Self::nyx_anise_de440s_bsp()) {
Ok(almanac) => {
info!("ANISE DE440S BSP has been loaded");
match almanac.load_from_metafile(Self::nyx_anise_pck11_pca()) {
Ok(almanac) => {
info!("ANISE PCK11 PCA has been loaded");
match almanac.load_from_metafile(Self::jpl_latest_high_prec_bpc()) {
Ok(almanac) => {
info!("JPL high precision (daily) kernels loaded.");
if let Ok(itrf93) = almanac.frame_from_uid(EARTH_ITRF93) {
info!("Deployed with highest precision context available.");
Ok((almanac, itrf93))
} else {
let iau_earth = almanac.frame_from_uid(IAU_EARTH_FRAME)?;
warn!("Failed to build ITRF93: relying on IAU model");
Ok((almanac, iau_earth))
}
},
Err(e) => {
let iau_earth = almanac.frame_from_uid(IAU_EARTH_FRAME)?;
error!("Failed to download JPL High precision kernels: {}", e);
warn!("Relying on IAU frame model.");
Ok((almanac, iau_earth))
},
}
},
Err(e) => {
let iau_earth = almanac.frame_from_uid(IAU_EARTH_FRAME)?;
error!("Failed to download PCK11 PCA: {}", e);
warn!("Relying on IAU frame model.");
Ok((almanac, iau_earth))
},
/// This will try to download the highest JPL model, and requires
/// internet access once a day.
/// If the JPL database cannot be accessed, we rely on an offline model.
fn build_almanac_frame_model() -> Result<(Almanac, Frame), Error> {
let mut initial_setup = false;

// Meta almanac for local storage management
let local_storage = format!(
"{}/{}/anise.dhall",
env!("CARGO_MANIFEST_DIR"),
Self::ALMANAC_LOCAL_STRORAGE
);

let mut meta_almanac = match MetaAlmanac::new(local_storage.clone()) {
Ok(meta) => {
debug!("(anise) from local storage");
meta
},
Err(_) => {
debug!("(anise) local storage setup");
initial_setup = true;
MetaAlmanac {
files: vec![
Self::nyx_anise_de440s_bsp(),
Self::nyx_anise_pck11_pca(),
Self::nyx_anise_jpl_bpc(),
],
}
},
};

// download (if need be)
let almanac = meta_almanac.process(true)?;

if initial_setup {
let updated = meta_almanac.dumps()?;

let _ = create_dir_all(&format!(
"{}/{}",
env!("CARGO_MANIFEST_DIR"),
Self::ALMANAC_LOCAL_STRORAGE
));

let mut fd = File::create(&local_storage)
.unwrap_or_else(|e| panic!("almanac storage setup error: {}", e));

fd.write_all(updated.as_bytes())
.unwrap_or_else(|e| panic!("almanac storage setup error: {}", e));
}

match almanac.frame_from_uid(EARTH_ITRF93) {
Ok(itrf93) => {
info!("highest precision context setup");
return Ok((almanac, itrf93));
},
Err(e) => {
error!("Failed to load DE440S BSP: {}", e);
let iau_earth = almanac.frame_from_uid(IAU_EARTH_FRAME)?;
warn!("Relying on IAU frame model.");
Ok((almanac, iau_earth))
error!("(anise) jpl_bpc: {}", e);
},
}

let earth_cef = almanac.frame_from_uid(IAU_EARTH_FRAME)?;
warn!("deployed with offline model");
Ok((almanac, earth_cef))
}

/// Create a new QcContext for which we will try to
/// retrieve the latest and highest precision [Almanac]
/// and reference [Frame] to work with. If you prefer
/// to manualy specify those, prefer the other constructor.
pub fn new() -> Result<Self, Error> {
let (almanac, earth_cef) = Self::build_almanac()?;
let (almanac, earth_cef) = Self::build_almanac_frame_model()?;
Ok(Self {
almanac,
earth_cef,
blob: HashMap::new(),
files: HashMap::new(),
})
}

/// Build new [QcContext] with given [Almanac] and desired [Frame],
/// which must be one of the available ECEF.
pub fn new_almanac(almanac: Almanac, frame: Frame) -> Result<Self, Error> {
Expand All @@ -250,6 +285,7 @@ impl QcContext {
files: HashMap::new(),
})
}

/// Returns main [TimeScale] for Self
pub fn timescale(&self) -> Option<TimeScale> {
#[cfg(feature = "sp3")]
Expand All @@ -274,6 +310,7 @@ impl QcContext {
None
}
}

/// Returns path to File considered as Primary product in this Context.
/// When a unique file had been loaded, it is obviously considered Primary.
pub fn primary_path(&self) -> Option<&PathBuf> {
Expand Down
4 changes: 4 additions & 0 deletions rinex-qc/src/plot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,16 @@ impl Plot {
show_legend: bool,
) -> Self {
let mut plotly = Plotly::new();

let layout = Layout::new()
.title(title)
.x_axis(Axis::new().title(x_label).zero_line(true))
.y_axis(Axis::new().title(y_label).zero_line(true))
.show_legend(show_legend)
.auto_size(true);

plotly.set_layout(layout);

Self {
plotly,
plot_id: plot_id.to_string(),
Expand Down
2 changes: 1 addition & 1 deletion rinex-qc/src/report/orbit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ impl OrbitReport {
MarkerSymbol::Circle,
None,
1.0,
true,
sv_index < 2,
);
map_proj.add_trace(map);
}
Expand Down
10 changes: 10 additions & 0 deletions rinex-qc/src/report/sp3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,16 @@ impl Render for SP3Report {
html! {
div class="table-container" {
table class="table is-bordered" {
tr {
th {
button aria-label="File revision" data-balloon-pos="right" {
"File revision"
}
}
td {
(self.version)
}
}
tr {
th {
button aria-label="Production Center" data-balloon-pos="right" {
Expand Down
6 changes: 3 additions & 3 deletions rinex/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ log = "0.4"
num = "0.4"
regex = "1"
strum = "0.26"
thiserror = "1"
thiserror = "2"
lazy_static = "1.4"
map_3d = "0.1.5"
num-derive = "0.4"
Expand All @@ -107,8 +107,8 @@ flate2 = { version = "1.0.24", optional = true, default-features = false, featur
geo = { version = "0.28", optional = true }
wkt = { version = "0.10.0", default-features = false, optional = true }

anise = { version = "=0.4.2", optional = true }
nalgebra = { version = "=0.32.3", optional = true }
anise = { version = "0.5.0", optional = true }
nalgebra = { version = "0.33.0", optional = true }
hifitime = { version = "4.0.0-beta", features = ["serde", "std"] }

# gnss-rs = { version = "2.2.3", features = ["serde", "domes", "cospar"] }
Expand Down
2 changes: 1 addition & 1 deletion rinex/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2341,7 +2341,7 @@ impl Rinex {
) -> Option<AzElRange> {
let sv_orbit = self.sv_orbit(sv, t)?;
let azelrange = almanac
.azimuth_elevation_range_sez(sv_orbit, rx_orbit)
.azimuth_elevation_range_sez(sv_orbit, rx_orbit, None, None)
.ok()?;
Some(azelrange)
}
Expand Down
9 changes: 5 additions & 4 deletions rinex/src/navigation/ephemeris.rs
Original file line number Diff line number Diff line change
Expand Up @@ -837,10 +837,11 @@ impl Ephemeris {
) -> AlmanacResult<AzElRange> {
let (rx_x_km, rx_y_km, rx_z_km) = rx_position_km;
let (tx_x_km, tx_y_km, tx_z_km) = sv_position_km;
almanac.azimuth_elevation_range_sez(
Orbit::from_position(tx_x_km, tx_y_km, tx_z_km, t, fixed_body_frame),
Orbit::from_position(rx_x_km, rx_y_km, rx_z_km, t, fixed_body_frame),
)

let rx_orbit = Orbit::from_position(rx_x_km, rx_y_km, rx_z_km, t, fixed_body_frame);
let tx_orbit = Orbit::from_position(tx_x_km, tx_y_km, tx_z_km, t, fixed_body_frame);

almanac.azimuth_elevation_range_sez(rx_orbit, tx_orbit, None, None)
}
/// Returns True if Self is Valid at specified `t`.
/// NB: this only applies to MEO Ephemerides, not GEO Ephemerides,
Expand Down
Binary file added test_resources/OBS/V3/OB713520.23O.gz
Binary file not shown.
Binary file added test_resources/OBS/V3/gps.23O.gz
Binary file not shown.
10 changes: 9 additions & 1 deletion tutorials/DIFF/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
DIFF
====

RINEX(A) - RINEX(B) differential analysis, example application.
RINEX(A) - RINEX(B) differential analysis and example applications.

RINEX differential analysis is useful to permit several yet different
and exotic measurements. For example, you can use phase differentiation to compare a local clock that is spread into two separate receivers. For that particular scenario, see our [phase-clock](./phase-clock.sh) example.

RINEX differentiation applies to all observations format.
[esbjrg-mojn](./esbjrg-mojn) is a demonstration of that.

Observations need to be made synchronously and we only differentiate identical observations (same physics, same signal and modulation).
9 changes: 3 additions & 6 deletions tutorials/DIFF/esbjrg-mojn.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#!/bin/sh
# OBS_RINEX(Esbjerg) - OBS_RINEX(Mojn)
# This operation is very powerful to compare two GNSS receivers setup.
# Especially when two clocks are synchronized or is split in the setup.
# To perform the differentiation, you need a common timeframe and common signals.
# For the lack of a better example, we use MOJNDNK and ESBJRG (DNK) that provided
# data on the same day, and happen to be very close to one another
# OBS_RINEX(Esbjerg) - OBS_RINEX(Mojn) (differential analysis)
# Two stations operated by the same agency, located within
# a few km of each other.
WORKSPACE=WORKSPACE
DATA_DIR=test_resources/CRNX/V3

Expand Down
16 changes: 16 additions & 0 deletions tutorials/DIFF/phase-clock.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/sh
# A clock is split into two separate receivers,
# sky is observed and we use identical GNSS RF signal/modulations
# to obtain the local clock behavior.
WORKSPACE=WORKSPACE
DATA_DIR=test_resources/OBS/V3

# Generate ""differenced"" observation RINEX=obs(A)-obs(B)
# diff is a file operations: a RINEX is dumped, no report synthesized.
./target/release/rinex-cli \
--fp $DATA_DIR/OB713520.23O.gz \
diff $DATA_DIR/gps.23O.gz

# Open previous results: generate a report
./target/release/rinex-cli \
--fp $WORKSPACE/OB713520/DIFFERENCED.23O.gz

0 comments on commit d0602bb

Please sign in to comment.