diff --git a/rinex/Cargo.toml b/rinex/Cargo.toml index 91d397b94..504012086 100644 --- a/rinex/Cargo.toml +++ b/rinex/Cargo.toml @@ -36,6 +36,7 @@ wkt = { version = "0.10.0", default-features = false, optional = true } serde = { version = "1.0", optional = true, default-features = false, features = ["derive"] } flate2 = { version = "1.0.24", optional = true, default-features = false, features = ["zlib"] } hifitime = { version = "3.7.0", features = ["serde", "std"] } +map_3d = "0.1.4" [dev-dependencies] criterion = "0.4" diff --git a/rinex/src/antex/frequency.rs b/rinex/src/antex/frequency.rs index 73212dd71..26cb8fe05 100644 --- a/rinex/src/antex/frequency.rs +++ b/rinex/src/antex/frequency.rs @@ -1,5 +1,5 @@ //! Antex - special RINEX type specific structures -use crate::channel; +use crate::channel::Channel; #[derive(Debug, Clone, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(Serialize))] @@ -48,7 +48,7 @@ impl Pattern { #[cfg_attr(feature = "serde", derive(Serialize))] pub struct Frequency { /// Channel, example: L1, L2 for GPS, E1, E5 for GAL... - pub channel: channel::Channel, + pub channel: Channel, /// Northern component of the mean antenna phase center /// relative to the antenna reference point (ARP), /// in [mm] @@ -70,7 +70,7 @@ pub struct Frequency { impl Default for Frequency { fn default() -> Self { Self { - channel: channel::Channel::default(), + channel: Channel::default(), north: 0.0_f64, east: 0.0_f64, up: 0.0_f64, @@ -80,7 +80,53 @@ impl Default for Frequency { } impl Frequency { - pub fn with_channel(&self, channel: channel::Channel) -> Self { + /// Returns ARP coordinates in Geodetic system. + /// Ref. position must be given in (lat, lon, altitude) [m] + pub fn arp_geodetic(&self, ref_pos: (f64, f64, f64)) -> (f64, f64, f64) { + map_3d::enu2geodetic( + self.east * 10.0E-3, + self.north * 10.0E-3, + self.up * 10.0E-3, + ref_pos.0, + ref_pos.1, + ref_pos.2, + map_3d::Ellipsoid::WGS84, + ) + } + /// Returns ARP coordinates in ECEF system. + pub fn arp_ecef(&self, ref_pos: (f64, f64, f64)) -> (f64, f64, f64) { + map_3d::enu2ecef( + self.east * 10.0E-3, + self.north * 10.0E-3, + self.up * 10.0E-3, + ref_pos.0, + ref_pos.1, + ref_pos.2, + map_3d::Ellipsoid::WGS84, + ) + } + /// Returns ARP coordinates as North Earth Down coordinates + pub fn arp_ned(&self, ref_pos: (f64, f64, f64)) -> (f64, f64, f64) { + let ecef = map_3d::enu2ecef( + self.east * 10.0E-3, + self.north * 10.0E-3, + self.up * 10.0E-3, + ref_pos.0, + ref_pos.1, + ref_pos.2, + map_3d::Ellipsoid::WGS84, + ); + map_3d::ecef2ned( + ecef.0, + ecef.1, + ecef.2, + ref_pos.0, + ref_pos.1, + ref_pos.2, + map_3d::Ellipsoid::WGS84, + ) + } + pub fn with_channel(&self, channel: Channel) -> Self { let mut f = self.clone(); f.channel = channel.clone(); f @@ -106,3 +152,22 @@ impl Frequency { f } } + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_pattern() { + let default = Pattern::default(); + assert_eq!(default, Pattern::NonAzimuthDependent(Vec::new())); + assert_eq!(default.is_azimuth_dependent(), false); + } + #[test] + fn test_frequency() { + let default = Frequency::default(); + assert_eq!(default.channel, Channel::default()); + assert_eq!(default.north, 0.0_f64); + assert_eq!(default.east, 0.0_f64); + assert_eq!(default.up, 0.0_f64); + } +} diff --git a/rinex/src/antex/pcv.rs b/rinex/src/antex/pcv.rs index ee83a6a16..f603fa695 100644 --- a/rinex/src/antex/pcv.rs +++ b/rinex/src/antex/pcv.rs @@ -54,3 +54,25 @@ impl Pcv { s } } + +#[cfg(test)] +mod test { + use super::*; + use std::str::FromStr; + #[test] + fn test_pcv() { + assert_eq!(Pcv::default(), Pcv::Absolute); + assert!(Pcv::Absolute.is_absolute()); + assert_eq!(Pcv::Relative(String::from("AOAD/M_T")).is_absolute(), false); + + let pcv = Pcv::from_str("A"); + assert!(pcv.is_ok()); + let pcv = pcv.unwrap(); + assert_eq!(pcv, Pcv::Absolute); + + let pcv = Pcv::from_str("R"); + assert!(pcv.is_ok()); + let pcv = pcv.unwrap(); + assert_eq!(pcv, Pcv::Relative(String::from("AOAD/M_T"))); + } +} diff --git a/rinex/src/channel.rs b/rinex/src/channel.rs index 6626b6ada..52bf236a4 100644 --- a/rinex/src/channel.rs +++ b/rinex/src/channel.rs @@ -278,16 +278,18 @@ impl Channel { mod test { use super::*; use std::str::FromStr; - /*#[test] - fn test_code() { - assert_eq!(Code::from_str("C1").is_ok(), true); - assert_eq!(Code::from_str("L1").is_err(), true); - assert_eq!(Code::from_str("P1").is_ok(), true); - }*/ #[test] fn test_channel() { - assert_eq!(Channel::from_str("L1").is_ok(), true); - assert_eq!(Channel::from_str("C1").is_err(), true); - assert_eq!(Channel::from_str("L5").is_ok(), true); + assert!(Channel::from_str("L1").is_ok()); + assert!(Channel::from_str("C1").is_err()); + assert!(Channel::from_str("L5").is_ok()); + + let l1 = Channel::from_str("L1").unwrap(); + assert_eq!(l1.carrier_frequency_mhz(), 1575.42_f64); + assert_eq!(l1.carrier_wavelength(), 299792458.0 / 1575.42_f64 / 10.0E6); + let channel = Channel::from_observable(Constellation::GPS, "L1C"); + assert!(channel.is_ok()); + let channel = channel.unwrap(); + assert_eq!(channel, Channel::L1); } } diff --git a/rinex/src/epoch/flag.rs b/rinex/src/epoch/flag.rs index 6cab0987e..0edba9614 100644 --- a/rinex/src/epoch/flag.rs +++ b/rinex/src/epoch/flag.rs @@ -101,4 +101,14 @@ mod test { assert_eq!(EpochFlag::from_str("6").unwrap(), EpochFlag::CycleSlip); assert!(EpochFlag::from_str("7").is_err()); } + #[test] + fn to_str() { + assert_eq!(format!("{}", EpochFlag::Ok), "0"); + assert_eq!(format!("{}", EpochFlag::PowerFailure), "1"); + assert_eq!(format!("{}", EpochFlag::AntennaBeingMoved), "2"); + assert_eq!(format!("{}", EpochFlag::NewSiteOccupation), "3"); + assert_eq!(format!("{}", EpochFlag::HeaderInformationFollows), "4"); + assert_eq!(format!("{}", EpochFlag::ExternalEvent), "5"); + assert_eq!(format!("{}", EpochFlag::CycleSlip), "6"); + } } diff --git a/rinex/src/hardware.rs b/rinex/src/hardware.rs index d56659e46..26ea631d8 100644 --- a/rinex/src/hardware.rs +++ b/rinex/src/hardware.rs @@ -138,3 +138,19 @@ impl SvAntenna { s } } + +#[cfg(test)] +mod test { + use super::*; + use std::str::FromStr; + #[test] + fn rcvr_parser() { + let content = "2090088 LEICA GR50 4.51 "; + let rcvr = Rcvr::from_str(content); + assert!(rcvr.is_ok()); + let rcvr = rcvr.unwrap(); + assert_eq!(rcvr.model, "LEICA GR50"); + assert_eq!(rcvr.sn, "2090088"); + assert_eq!(rcvr.firmware, "4.51"); + } +} diff --git a/rinex/src/ionex/grid.rs b/rinex/src/ionex/grid.rs index b830e426c..18e3e37d4 100644 --- a/rinex/src/ionex/grid.rs +++ b/rinex/src/ionex/grid.rs @@ -1,8 +1,9 @@ -#[cfg(feature = "serde")] -use serde::Serialize; use std::ops::Rem; use thiserror::Error; +#[cfg(feature = "serde")] +use serde::Serialize; + /// Grid definition Error #[derive(Error, Debug)] pub enum Error { @@ -18,11 +19,11 @@ pub enum Error { #[derive(Debug, Clone, Default, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct GridLinspace { - /// Grid start value, in km + /// Grid start coordinates [ddeg] pub start: f32, - /// Grid end value, in km + /// Grid end coordinates [ddeg] pub end: f32, - /// Grid scaping / increment value, in km + /// Grid spacing (inncrement value), [ddeg] pub spacing: f32, } @@ -30,6 +31,9 @@ impl GridLinspace { /// Builds a new Linspace definition pub fn new(start: f32, end: f32, spacing: f32) -> Result { let r = end.rem(start); + /* + * End / Start must be multiple of one another + */ if r == 0.0 { if end.rem(spacing) == 0.0 { Ok(Self { @@ -44,10 +48,9 @@ impl GridLinspace { Err(Error::GridStartEndError) } } - // Returns total distance, in km, covered by - // this Grid linear space - pub fn total_distance(&self) -> f32 { - (self.end - self.start) * self.spacing + // Returns grid length, in terms of data points + pub fn length(&self) -> usize { + (self.end / self.spacing).floor() as usize } /// Returns true if self is a single point space pub fn is_single_point(&self) -> bool { @@ -91,8 +94,24 @@ impl Grid { pub fn is_2d_grid(&self) -> bool { self.height.is_single_point() } - /// Returns total projected 2D area covered [kmĀ²] - pub fn total_area(&self) -> f32 { - self.latitude.total_distance() * self.longitude.total_distance() +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_grid() { + let default = GridLinspace::default(); + assert_eq!( + default, + GridLinspace { + start: 0.0, + end: 0.0, + spacing: 0.0, + } + ); + let grid = GridLinspace::new(1.0, 10.0, 1.0).unwrap(); + assert_eq!(grid.length(), 10); + assert_eq!(grid.is_single_point(), false); } } diff --git a/rinex/src/ionex/system.rs b/rinex/src/ionex/system.rs index 57867aa5b..d2fe54667 100644 --- a/rinex/src/ionex/system.rs +++ b/rinex/src/ionex/system.rs @@ -114,3 +114,16 @@ impl FromStr for RefSystem { } } } + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_refsystem() { + let default = RefSystem::default(); + assert_eq!( + default, + RefSystem::GnssConstellation(Constellation::default()) + ); + } +} diff --git a/rinex/src/navigation/health.rs b/rinex/src/navigation/health.rs index 9c68d8bdd..32dec2b4e 100644 --- a/rinex/src/navigation/health.rs +++ b/rinex/src/navigation/health.rs @@ -91,7 +91,7 @@ pub enum GloHealth { impl Default for GloHealth { fn default() -> Self { - Self::Healthy + Self::Unhealthy } } @@ -119,3 +119,32 @@ bitflags! { const E5B_HS1 = 0x80; } } + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_gps() { + assert_eq!(Health::default(), Health::Unhealthy); + assert_eq!(format!("{:E}", Health::default()), "0E0"); + } + #[test] + fn test_irnss() { + assert_eq!(IrnssHealth::default(), IrnssHealth::Unknown); + assert_eq!(format!("{:E}", IrnssHealth::default()), "1E0"); + } + #[test] + fn test_geo_sbas() { + assert_eq!(GeoHealth::default(), GeoHealth::Unknown); + assert_eq!(format!("{:E}", Health::default()), "0E0"); + } + #[test] + fn test_glo() { + assert_eq!(GloHealth::default(), GloHealth::Unhealthy); + assert_eq!(format!("{:E}", GloHealth::default()), "4E0"); + } + #[test] + fn test_gal() { + assert_eq!(GalHealth::default(), GalHealth::empty()); + } +} diff --git a/rinex/src/navigation/ionmessage.rs b/rinex/src/navigation/ionmessage.rs index 61e5176b3..517039b24 100644 --- a/rinex/src/navigation/ionmessage.rs +++ b/rinex/src/navigation/ionmessage.rs @@ -303,3 +303,70 @@ impl IonMessage { } } } + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_kb() { + assert_eq!(KbRegionCode::default(), KbRegionCode::WideArea); + let content = + " 2022 06 08 09 59 48 1.024454832077E-08 2.235174179077E-08-5.960464477539E-08 + -1.192092895508E-07 9.625600000000E+04 1.310720000000E+05-6.553600000000E+04 + -5.898240000000E+05 0.000000000000E+00"; + let mut content = content.lines(); + let parsed = KbModel::parse(content); + assert!(parsed.is_ok()); + let (epoch, message) = parsed.unwrap(); + assert_eq!( + epoch, + Epoch::from_gregorian_utc(2022, 06, 08, 09, 59, 48, 00) + ); + assert_eq!( + message, + KbModel { + alpha: ( + 1.024454832077E-08, + 2.235174179077E-08, + -5.960464477539E-08, + -1.192092895508E-07 + ), + beta: ( + 9.625600000000E+04, + 1.310720000000E+05, + -6.553600000000E+04, + -5.898240000000E+05 + ), + region: KbRegionCode::WideArea, + }, + ); + } + #[test] + fn test_ng() { + let content = + " 2022 06 08 09 59 57 7.850000000000E+01 5.390625000000E-01 2.713012695312E-02 + 0.000000000000E+00"; + let mut content = content.lines(); + let parsed = NgModel::parse(content); + assert!(parsed.is_ok()); + let (epoch, message) = parsed.unwrap(); + assert_eq!( + epoch, + Epoch::from_gregorian_utc(2022, 06, 08, 09, 59, 57, 00) + ); + assert_eq!( + message, + NgModel { + a: (7.850000000000E+01, 5.390625000000E-01, 2.713012695312E-02), + region: NgRegionFlags::empty(), + }, + ); + } + #[test] + fn test_ionmessage() { + let msg = IonMessage::KlobucharModel(KbModel::default()); + assert!(msg.as_klobuchar().is_some()); + assert!(msg.as_nequick_g().is_none()); + assert!(msg.as_bdgim().is_none()); + } +}