Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to ANISE 0.5.0 #281

Merged
merged 7 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading