Skip to content

Commit

Permalink
Fix offline/online deployment options
Browse files Browse the repository at this point in the history
  - a new --jpl-bpc option is introduced in RINEX-Cli
  Which always requires internet access and IS THE ONLY OPTION
  to keep the JPL model up to date.

  - the local storage is preserved and allows fast deployments
  (after a first successful deployment)

  - for example, in the past few days the JPL servers have been down.
  when deploying without --jpl-bpc:
    - the session will rely on the previous JPL model if it was already downloaded.
    If this model is too long and servers remain down, you should delete it with!
    - the session will rely on low precision (offline) model until a JPL model
    has been obtained

  When deploying with --jpl-bpc:
    - the session will crash because correct network access is expected

Signed-off-by: Guillaume W. Bres <[email protected]>
  • Loading branch information
gwbres committed Jan 10, 2025
1 parent 01a6925 commit 4081910
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 78 deletions.
13 changes: 13 additions & 0 deletions rinex-cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ to operate the RINEX/SP3/RTK toolkit, until a GUI is made available.
Use it to analyze data, perform file operations and resolve navigation solutions.")
.arg_required_else_help(true)
.color(ColorChoice::Always)
.next_help_heading("Context")
.arg(Arg::new("filepath")
.long("fp")
.value_name("FILE")
Expand Down Expand Up @@ -182,6 +183,13 @@ but you can extend that with --depth. Refer to -f for more information."))
By default the $RINEX_WORKSPACE variable is prefered if it is defined.
You can also use this flag to customize it.
If none are defined, we will then try to create a local directory named \"WORKSPACE\" like it is possible in this very repo."))
.arg(Arg::new("jpl-bpc")
.long("jpl-bpc")
.action(ArgAction::SetTrue)
.help("Force update or request upgrade to highest precision JPL daily model.
Requires internet access on each deployment!
Once downloaded (updated) a model is valid for a couple of days or weeks, but you should regularly update
to obtain highest precision."))
.next_help_heading("Output customization")
.arg(
Arg::new("output-name")
Expand Down Expand Up @@ -482,4 +490,9 @@ Otherwise it gets automatically picked up."))
pub fn custom_output_name(&self) -> Option<&String> {
self.matches.get_one::<String>("output-name")
}

/// True if jpl_bpc_update is requested
pub fn jpl_bpc_update(&self) -> bool {
self.matches.get_flag("jpl-bpc")
}
}
4 changes: 2 additions & 2 deletions rinex-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ fn user_data_parsing(
max_depth: usize,
is_rover: bool,
) -> QcContext {
let mut ctx =
QcContext::new().unwrap_or_else(|e| panic!("context setup issue: {}", e));
let mut ctx = QcContext::new(cli.jpl_bpc_update())
.unwrap_or_else(|e| panic!("failed to initialize a context: {}", e));

// recursive dir loader
for dir in directories.iter() {
Expand Down
247 changes: 171 additions & 76 deletions rinex-qc/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ use qc_traits::{
/// Context Error
#[derive(Debug, Error)]
pub enum Error {
#[error("almanac error")]
Alamanac(#[from] AlmanacError),
#[error("almanac setup error")]
MetaAlamanac(#[from] MetaAlmanacError),
#[error("almanac error: {0}")]
Almanac(#[from] AlmanacError),
#[error("meta error: {0}")]
MetaAlmanac(#[from] MetaAlmanacError),
#[error("planetary data error")]
PlanetaryData(#[from] PlanetaryDataError),
#[error("failed to extend gnss context")]
Expand Down Expand Up @@ -169,11 +169,11 @@ pub struct QcContext {
}

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

fn nyx_anise_de440s_bsp() -> MetaFile {
MetaFile {
crc32: Some(1921414410),
crc32: Some(0x7286750a),
uri: String::from("http://public-data.nyxspace.com/anise/de440s.bsp"),
}
}
Expand All @@ -194,96 +194,191 @@ impl QcContext {
}
}

/// Method to either download, retrieve or create
/// a basic [Almanac] and reference [Frame] to work with.
/// 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;
/// This [MetaAlmanac] solely relies on the nyx-space servers
fn low_prec_meta() -> MetaAlmanac {
MetaAlmanac {
files: vec![Self::nyx_anise_pck11_pca(), Self::nyx_anise_de440s_bsp()],
}
}

/// This [MetaAlmanac] relies on the nyx-space servers
/// and the daily JPL update
fn high_prec_meta() -> MetaAlmanac {
MetaAlmanac {
files: vec![
Self::nyx_anise_de440s_bsp(),
Self::nyx_anise_pck11_pca(),
Self::nyx_anise_jpl_bpc(),
],
}
}

// Meta almanac for local storage management
/// Creates a new [QcContext].
/// Requires network access on first deployment ever (otherwise will fail)
/// because initial deployment will initiate a local storage
/// which makes future deployments much faster.
///
/// ## Input
/// - "jpl_update" == false
/// When "update is set to false, this session will rely either on:
/// - previously downloaded high precision model
/// - previously downloaded low precision model (if "update" has never been used)
///
/// - "jpl_update" == true
/// Each call will require valid internet access otherwise will fail.
/// We attempt the upgrade (or initial setup) of the highest precision model.
/// The model is valid for up to 3 months, in practice you can safely use it
/// for a couple of days or weeks, until network is available for a new update.
pub fn new(jpl_update: bool) -> Result<Self, Error> {
let local_storage = format!(
"{}/{}/anise.dhall",
env!("CARGO_MANIFEST_DIR"),
Self::ALMANAC_LOCAL_STRORAGE
Self::ALMANAC_LOCAL_STORAGE
);

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(),
],
}
},
};
// if the "".dhall"" doesnt exist, this is first deployment ever
let anise_exist = Path::new(&local_storage).exists();

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

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

let _ = create_dir_all(&format!(
let almanac = if !anise_exist {
// prepare storage
create_dir_all(&format!(
"{}/{}",
env!("CARGO_MANIFEST_DIR"),
Self::ALMANAC_LOCAL_STRORAGE
));
Self::ALMANAC_LOCAL_STORAGE
))
.unwrap_or_else(|e| panic!("almanac storage setup error: {}", e));

let mut fd = File::create(&local_storage)
.unwrap_or_else(|e| panic!("almanac storage setup error: {}", e));
let almanac = Self::first_almanac_ever(&local_storage, jpl_update)?;
uses_jpl_model = true;
almanac
} else {
// using local storage
let mut meta = match MetaAlmanac::new(local_storage.clone()) {
Ok(meta) => meta,
Err(e) => {
error!("(anise) failed to retrieve local storage!");
return Err(Error::MetaAlmanac(e));
},
};

// force JPL update
if jpl_update {
for file in &mut meta.files {
if file.uri.contains("earth_latest_high_prec.bpc") {
file.crc32 = None;
uses_jpl_model = true;
break;
}
}

fd.write_all(updated.as_bytes())
.unwrap_or_else(|e| panic!("almanac storage setup error: {}", e));
}
if !uses_jpl_model {
meta.files.push(Self::nyx_anise_jpl_bpc());
}
}

match almanac.frame_from_uid(EARTH_ITRF93) {
Ok(itrf93) => {
info!("highest precision context setup");
return Ok((almanac, itrf93));
},
Err(e) => {
error!("(anise) jpl_bpc: {}", e);
},
}
match meta.process(true) {
Ok(almanac) => {
if jpl_update {
info!("(anise) jpl latest high precision model updated");

// try to save for later (upgrading the local storage)
match meta.dumps() {
Ok(serialized) => {
let mut fd = File::create(&local_storage).unwrap_or_else(|e| {
panic!("almanac storage setup error: {}", e)
});

fd.write_all(serialized.as_bytes()).unwrap_or_else(|e| {
panic!("almanac storage setup error: {}", e)
});
},
Err(e) => {
error!("(anise) almanac serialization failed: {}", e)
},
}
} else {
info!("(anise) using previous model");
}
almanac
},
Err(e) => {
// upgrade failed
error!("(anise) context setup error: {}", e);
return Err(Error::Almanac(e));
},
}
};

let earth_cef = almanac.frame_from_uid(IAU_EARTH_FRAME)?;
warn!("deployed with offline model");
Ok((almanac, earth_cef))
}
// always try to obtain the ITRF93 frame
// which succeeds on ideal scenario, and may fail
// depending on deployment status
let earth_cef = if uses_jpl_model {
match almanac.frame_from_uid(EARTH_ITRF93) {
Ok(ecef) => {
info!("(anise) deployed with high precision model");
ecef
},
Err(e) => {
error!("(anise) failed to deploy high precision model: {}", e);
return Err(Error::PlanetaryData(e));
},
}
} else {
match almanac.frame_from_uid(IAU_EARTH_FRAME) {
Ok(itrf93) => {
info!("(anise) deployed with low precision model");
itrf93
},
Err(e) => {
error!("(anise) failed to deploy low precision model: {}", e);
return Err(Error::PlanetaryData(e));
},
}
};

/// 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_frame_model()?;
Ok(Self {
almanac,
earth_cef,
blob: HashMap::new(),
files: HashMap::new(),
almanac,
files: Default::default(),
blob: Default::default(),
})
}

/// 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> {
Ok(Self {
almanac,
earth_cef: frame,
blob: HashMap::new(),
files: HashMap::new(),
})
// Case of first deployment ever
fn first_almanac_ever(local_storage: &str, jpl_update: bool) -> Result<Almanac, Error> {
let mut meta = if jpl_update {
Self::high_prec_meta()
} else {
Self::low_prec_meta()
};

let almanac = meta.process(true)?;

if jpl_update {
info!("(anise) high precision almanac initiated");
} else {
info!("(anise) low precision almanac initiated");
}

// setup backup storage for faster future deployments
match meta.dumps() {
Ok(serialized) => {
// backup storage for faster future deployments
let mut fd = File::create(&local_storage)
.unwrap_or_else(|e| panic!("almanac storage setup error: {}", e));

fd.write_all(serialized.as_bytes())
.unwrap_or_else(|e| panic!("almanac storage setup error: {}", e));
},
Err(e) => {
error!("(anise) serialization error: {}", e);
warn!("(anise) local storage not initiated");
},
}

Ok(almanac)
}

/// Returns main [TimeScale] for Self
Expand Down

0 comments on commit 4081910

Please sign in to comment.