Skip to content

Commit

Permalink
DOMES site identifier support (#217)
Browse files Browse the repository at this point in the history
* DOMES ID# is how IGN/ITRF identifies a geodetic site
  * Several RINEX files use these numbers

Signed-off-by: Guillaume W. Bres <[email protected]>
  • Loading branch information
gwbres authored Mar 24, 2024
1 parent df03742 commit 3e49f90
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 74 deletions.
2 changes: 2 additions & 0 deletions rinex/src/bibliography.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ pub enum Bibliography {
/// GNSS antennas*.
/// [DOI](https://cdn.taoglas.com/wp-content/uploads/pdf/Multipath-Analysis-Using-Code-Minus-Carrier-Technique-in-GNSS-Antennas-_WhitePaper_VP__Final-1.pdf).
MpTaoglas,
/// [IGN/ITRF DOMES Site Identifier](https://itrf.ign.fr/en/network/domes/description)
IgnItrfDomes,
}
43 changes: 27 additions & 16 deletions rinex/src/clock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ pub use record::{ClockKey, ClockProfile, ClockProfileType, ClockType, Error, Rec

use crate::version::Version;
use hifitime::TimeScale;
use std::str::FromStr;

use crate::domes::Domes;

/// Clocks `RINEX` specific header fields
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct HeaderFields {
/// Site name
pub site: Option<String>,
/// Unique Site-ID (DOMES number)
pub site_id: Option<String>,
/// Site DOMES ID#
pub domes: Option<Domes>,
/// IGS code
pub igs: Option<String>,
/// Full name
Expand All @@ -35,8 +38,8 @@ pub struct HeaderFields {
pub struct WorkClock {
/// Name of this local clock
pub name: String,
/// Unique identifier (DOMES number)
pub id: String,
/// Clock site DOMES ID#
pub domes: Option<Domes>,
/// Possible clock constraint [s]
pub constraint: Option<f64>,
}
Expand All @@ -46,11 +49,15 @@ impl WorkClock {
const LIMIT: Version = Version { major: 3, minor: 4 };
if version < LIMIT {
let (name, rem) = content.split_at(4);
let (id, rem) = rem.split_at(36);
let (domes, rem) = rem.split_at(36);
let constraint = rem.split_at(20).0;
Self {
name: name.trim().to_string(),
id: id.trim().to_string(),
domes: if let Ok(domes) = Domes::from_str(domes.trim()) {
Some(domes)
} else {
None
},
constraint: if let Ok(value) = constraint.trim().parse::<f64>() {
Some(value)
} else {
Expand All @@ -59,11 +66,15 @@ impl WorkClock {
}
} else {
let (name, rem) = content.split_at(10);
let (id, rem) = rem.split_at(10);
let (domes, rem) = rem.split_at(10);
let constraint = rem.split_at(40).0;
Self {
name: name.trim().to_string(),
id: id.trim().to_string(),
domes: if let Ok(domes) = Domes::from_str(domes.trim()) {
Some(domes)
} else {
None
},
constraint: if let Ok(value) = constraint.trim().parse::<f64>() {
Some(value)
} else {
Expand All @@ -75,37 +86,37 @@ impl WorkClock {
}

impl HeaderFields {
pub fn work_clock(&self, clk: WorkClock) -> Self {
pub(crate) fn work_clock(&self, clk: WorkClock) -> Self {
let mut s = self.clone();
s.work_clock.push(clk);
s
}
pub fn timescale(&self, ts: TimeScale) -> Self {
pub(crate) fn timescale(&self, ts: TimeScale) -> Self {
let mut s = self.clone();
s.timescale = Some(ts);
s
}
pub fn site(&self, site: &str) -> Self {
pub(crate) fn site(&self, site: &str) -> Self {
let mut s = self.clone();
s.site = Some(site.to_string());
s
}
pub fn site_id(&self, siteid: &str) -> Self {
pub(crate) fn domes(&self, domes: Domes) -> Self {
let mut s = self.clone();
s.site_id = Some(siteid.to_string());
s.domes = Some(domes);
s
}
pub fn igs(&self, igs: &str) -> Self {
pub(crate) fn igs(&self, igs: &str) -> Self {
let mut s = self.clone();
s.igs = Some(igs.to_string());
s
}
pub fn full_name(&self, name: &str) -> Self {
pub(crate) fn full_name(&self, name: &str) -> Self {
let mut s = self.clone();
s.full_name = Some(name.to_string());
s
}
pub fn refclock(&self, clk: &str) -> Self {
pub(crate) fn refclock(&self, clk: &str) -> Self {
let mut s = self.clone();
s.ref_clock = Some(clk.to_string());
s
Expand Down
117 changes: 117 additions & 0 deletions rinex/src/domes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use thiserror::Error;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

#[cfg(docrs)]
pub use bibliography::Bibliography;

/// DOMES parsing error
#[derive(Debug, Error)]
pub enum Error {
#[error("invalid domes format")]
InvalidFormat,
#[error("invalid domes length")]
InvalidLength,
}

/// DOMES site reference point.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TrackingPoint {
/// Monument (pole, pillar, geodetic marker..)
Monument,
/// Instrument reference point.
/// This is usually the antenna reference point, but it can be any
/// location referred to an instrument, like a specific location
/// on one axis of a telescope.
Instrument,
}

/// DOMES Site identifier, see [Bibliography::IgnItrfDomes]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Domes {
/// Area / Country code (3 digits)
pub area: u16,
/// Area site number (2 digits)
pub site: u8,
/// Tracking point
pub point: TrackingPoint,
/// Sequential number (3 digits)
pub sequential: u16,
}

impl std::str::FromStr for Domes {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() == 9 {
let point = if s[5..6].eq("M") {
TrackingPoint::Monument
} else if s[5..6].eq("S") {
TrackingPoint::Instrument
} else {
return Err(Error::InvalidFormat);
};
let area = s[..3].parse::<u16>().map_err(|_| Error::InvalidFormat)?;
let site = s[3..5].parse::<u8>().map_err(|_| Error::InvalidFormat)?;
let sequential = s[6..].parse::<u16>().map_err(|_| Error::InvalidFormat)?;
Ok(Self {
point,
area,
site,
sequential,
})
} else {
Err(Error::InvalidLength)
}
}
}

impl std::fmt::Display for Domes {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let point = match self.point {
TrackingPoint::Monument => 'M',
TrackingPoint::Instrument => 'S',
};
write!(
f,
"{:03}{:02}{}{:03}",
self.area, self.site, point, self.sequential
)
}
}

#[cfg(test)]
mod test {
use super::{Domes, TrackingPoint};
use std::str::FromStr;
#[test]
fn parser() {
for (descriptor, expected) in [
(
"10002M006",
Domes {
area: 100,
site: 2,
sequential: 6,
point: TrackingPoint::Monument,
},
),
(
"40405S031",
Domes {
area: 404,
site: 5,
sequential: 31,
point: TrackingPoint::Instrument,
},
),
] {
let domes = Domes::from_str(descriptor).unwrap();
assert_eq!(domes, expected, "failed to parse DOMES");
// reciprocal
assert_eq!(domes.to_string(), descriptor, "DOMES reciprocal failed");
}
}
}
Loading

0 comments on commit 3e49f90

Please sign in to comment.