165 lines
5.3 KiB
Rust
165 lines
5.3 KiB
Rust
//! The access window type and its summary.
|
|
|
|
use apsis_math::Vec3;
|
|
|
|
/// One continuous interval during which a satellite is above a station's
|
|
/// elevation mask.
|
|
///
|
|
/// Times are seconds from an origin chosen by the caller, so the same
|
|
/// structure serves a search over a scenario clock and one over elapsed
|
|
/// time.
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub struct Pass {
|
|
/// The time the satellite rises above the mask, in seconds from the
|
|
/// caller's origin.
|
|
pub rise_seconds: f64,
|
|
/// The time the satellite falls below the mask.
|
|
pub set_seconds: f64,
|
|
/// The time of greatest elevation.
|
|
pub peak_seconds: f64,
|
|
/// The greatest elevation during the pass, in radians.
|
|
pub peak_elevation: f64,
|
|
/// The elevation at which the satellite crossed the mask, in radians.
|
|
///
|
|
/// This is the station's mask, recorded so that a pass carries the
|
|
/// geometry it was found under.
|
|
pub mask_elevation: f64,
|
|
/// The satellite position in the Earth-fixed frame at the peak, in
|
|
/// kilometers.
|
|
pub peak_position: Vec3,
|
|
}
|
|
|
|
impl Pass {
|
|
/// Returns the duration of the pass, in seconds.
|
|
pub fn duration(&self) -> f64 {
|
|
self.set_seconds - self.rise_seconds
|
|
}
|
|
|
|
/// Returns the time from the rise to the peak, in seconds.
|
|
pub fn rise_to_peak(&self) -> f64 {
|
|
self.peak_seconds - self.rise_seconds
|
|
}
|
|
|
|
/// Returns the elevation at a time within the pass, when the caller can
|
|
/// supply it.
|
|
///
|
|
/// The pass stores only the peak, so this helper reports the stored
|
|
/// values at the three defining times and defers to the caller's
|
|
/// elevation function otherwise.
|
|
pub fn contains(&self, seconds: f64) -> bool {
|
|
seconds >= self.rise_seconds && seconds <= self.set_seconds
|
|
}
|
|
|
|
/// Returns the maximum elevation of the pass, in radians.
|
|
///
|
|
/// This is the same as the peak elevation, named for readability at a
|
|
/// call site that compares passes.
|
|
pub fn maximum_elevation(&self) -> f64 {
|
|
self.peak_elevation
|
|
}
|
|
}
|
|
|
|
/// A summary of the access windows over an interval.
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct PassSummary {
|
|
/// The passes, in time order.
|
|
pub passes: Vec<Pass>,
|
|
/// The length of the interval searched, in seconds.
|
|
pub interval_seconds: f64,
|
|
}
|
|
|
|
impl PassSummary {
|
|
/// Creates a summary from a list of passes and the interval length.
|
|
pub fn new(passes: Vec<Pass>, interval_seconds: f64) -> Self {
|
|
Self {
|
|
passes,
|
|
interval_seconds,
|
|
}
|
|
}
|
|
|
|
/// Returns the number of passes.
|
|
pub fn count(&self) -> usize {
|
|
self.passes.len()
|
|
}
|
|
|
|
/// Returns the total access time, in seconds.
|
|
///
|
|
/// The passes are disjoint, so the durations add.
|
|
pub fn total_access_seconds(&self) -> f64 {
|
|
self.passes.iter().map(Pass::duration).sum()
|
|
}
|
|
|
|
/// Returns the fraction of the interval during which the satellite was
|
|
/// accessible.
|
|
///
|
|
/// Returns `None` when the interval length is not positive.
|
|
pub fn access_fraction(&self) -> Option<f64> {
|
|
if self.interval_seconds > 0.0 && self.interval_seconds.is_finite() {
|
|
Some(self.total_access_seconds() / self.interval_seconds)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Returns the mean pass duration, in seconds.
|
|
///
|
|
/// Returns `None` when there are no passes.
|
|
pub fn mean_duration_seconds(&self) -> Option<f64> {
|
|
if self.passes.is_empty() {
|
|
return None;
|
|
}
|
|
Some(self.total_access_seconds() / self.passes.len() as f64)
|
|
}
|
|
|
|
/// Returns the longest pass, if there is one.
|
|
pub fn longest(&self) -> Option<&Pass> {
|
|
self.passes.iter().max_by(|first, second| {
|
|
first
|
|
.duration()
|
|
.partial_cmp(&second.duration())
|
|
.unwrap_or(core::cmp::Ordering::Equal)
|
|
})
|
|
}
|
|
|
|
/// Returns the highest pass, if there is one.
|
|
pub fn highest(&self) -> Option<&Pass> {
|
|
self.passes.iter().max_by(|first, second| {
|
|
first
|
|
.peak_elevation
|
|
.partial_cmp(&second.peak_elevation)
|
|
.unwrap_or(core::cmp::Ordering::Equal)
|
|
})
|
|
}
|
|
|
|
/// Returns the gap between two consecutive passes, in seconds.
|
|
///
|
|
/// Returns `None` when either index is out of range or the two indices
|
|
/// are not consecutive and increasing.
|
|
pub fn gap_seconds(&self, first: usize, second: usize) -> Option<f64> {
|
|
if second != first + 1 {
|
|
return None;
|
|
}
|
|
let earlier = self.passes.get(first)?;
|
|
let later = self.passes.get(second)?;
|
|
Some(later.rise_seconds - earlier.set_seconds)
|
|
}
|
|
|
|
/// Returns the largest gap between consecutive passes, in seconds.
|
|
///
|
|
/// This is the longest time a station waits between contacts.
|
|
/// Returns `None` when there are fewer than two passes.
|
|
pub fn longest_gap_seconds(&self) -> Option<f64> {
|
|
if self.passes.len() < 2 {
|
|
return None;
|
|
}
|
|
self.passes
|
|
.windows(2)
|
|
.map(|pair| pair[1].rise_seconds - pair[0].set_seconds)
|
|
.max_by(|first, second| {
|
|
first
|
|
.partial_cmp(second)
|
|
.unwrap_or(core::cmp::Ordering::Equal)
|
|
})
|
|
}
|
|
}
|