apsis-rs/crates/network/apsis-constellation/src/walker.rs

487 lines
19 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! The Walker constellation pattern and its instantiation.
//!
//! Walker 星座构型及其实例化。
use crate::error::{ConstellationError, ConstellationErrorKind};
use apsis_kepler::KeplerianElements;
use core::f64::consts::PI;
/// A Walker delta pattern.
///
/// A Walker pattern places a number of satellites evenly around the Earth in
/// a number of planes that share an inclination and a semi-major axis. The
/// relative phasing of adjacent planes is set by the phase factor, so the
/// whole constellation is described by five numbers.
///
/// Walker 构型把若干卫星均匀分布在绕地球的若干轨道面上,各面共用同一倾角与
/// 同一半长轴。相邻轨道面之间的相对相位由相位因子决定,因此整个星座由五个数
/// 描述。
///
/// The pattern is written `i: t/p/f`, where `i` is the inclination, `t` the
/// total number of satellites, `p` the number of planes, and `f` the phase
/// factor. This structure stores the same quantities, with the inclination
/// in radians and the semi-major axis in kilometers.
///
/// 构型写作 `i: t/p/f`,其中 `i` 为倾角、`t` 为卫星总数、`p` 为轨道面数、
/// `f` 为相位因子。本结构体存储同样的量,倾角用弧度,半长轴用千米。
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WalkerPattern {
/// The total number of satellites.
///
/// 卫星总数。
pub total_satellites: usize,
/// The number of orbital planes.
///
/// 轨道面数。
pub planes: usize,
/// The phase factor, in the range `0..planes`.
///
/// 相位因子,取值范围 `0..planes`。
pub phase_factor: usize,
/// The inclination of every plane, in radians.
///
/// 各轨道面的倾角,单位为弧度。
pub inclination: f64,
/// The semi-major axis of every satellite, in kilometers.
///
/// 各卫星的半长轴,单位为千米。
pub semi_major_axis: f64,
}
impl WalkerPattern {
/// Returns the number of satellites in each plane.
///
/// Returns `None` when the satellite count is not a multiple of the
/// plane count.
///
/// 返回每个轨道面上的卫星数。
///
/// 当卫星总数不是轨道面数的整数倍时返回 `None`。
pub fn satellites_per_plane(self) -> Option<usize> {
if self.planes == 0 || self.total_satellites % self.planes != 0 {
return None;
}
Some(self.total_satellites / self.planes)
}
/// Returns the spacing between adjacent planes in right ascension, in
/// radians.
///
/// Returns `None` when the plane count is zero, which is the only case
/// in which the spacing is undefined.
///
/// The spacing does not depend on the pattern being consistent, so a
/// caller can report the value it would use even for a pattern that
/// cannot be instantiated.
///
/// 返回相邻轨道面之间的赤经间隔,单位为弧度,即 `360 / planes` 度。
///
/// 当轨道面数为零时返回 `None`,这是该间隔未定义的唯一情形。
///
/// 该间隔不依赖构型是否自洽,因此即使构型无法实例化,调用方也能报告它本会
/// 使用的值。
pub fn plane_spacing(self) -> Option<f64> {
(self.planes > 0).then(|| 2.0 * PI / self.planes as f64)
}
/// Returns the phase offset applied between adjacent planes, in radians.
///
/// This is `f * 360 / t` degrees, where `f` is the phase factor and `t`
/// the total satellite count.
///
/// Returns `None` when the satellite count is zero, which is the only
/// case in which the offset is undefined.
///
/// 返回相邻轨道面之间施加的相位偏移,单位为弧度。
///
/// 即 `f * 360 / t` 度,其中 `f` 为相位因子,`t` 为卫星总数。
///
/// 当卫星总数为零时返回 `None`,这是该偏移未定义的唯一情形。
pub fn phase_offset(self) -> Option<f64> {
if self.total_satellites == 0 {
return None;
}
Some(2.0 * PI * self.phase_factor as f64 / self.total_satellites as f64)
}
/// Returns `true` when the pattern can be instantiated.
///
/// 当该构型可以被实例化时返回 `true`。
pub fn is_valid(self) -> bool {
self.total_satellites > 0
&& self.planes > 0
&& self.total_satellites % self.planes == 0
&& self.phase_factor < self.planes
&& self.inclination.is_finite()
&& (0.0..=PI).contains(&self.inclination)
&& self.semi_major_axis.is_finite()
&& self.semi_major_axis > 0.0
}
/// Returns the Walker notation for this pattern, such as `53: 24/3/1`.
///
/// The inclination is printed at full precision, so a value such as
/// 53.25 degrees survives the round trip. The notation is an identifier
/// rather than a rounded quotation, so truncating it would lose
/// information for no gain.
///
/// 返回该构型的 Walker 记法,例如 `53: 24/3/1`。
///
/// 倾角按全精度打印,因此 53.25 度这样的值可以原样往返。该记法用作标识而
/// 非取整后的引用值,截断它只会丢失信息而没有收益。
pub fn notation(self) -> String {
format!(
"{}: {}/{}/{}",
self.inclination.to_degrees(),
self.total_satellites,
self.planes,
self.phase_factor
)
}
/// Instantiates the pattern as a list of orbital elements.
///
/// The mean anomaly of the reference satellite of the first plane is
/// zero. Each further satellite in a plane is advanced by
/// `360 / (t / p)` degrees, and each further plane is advanced by the
/// phase offset.
///
/// Returns an error describing why the pattern cannot be instantiated.
///
/// Source: the phasing rule is the one defined in Walker, J. G.,
/// *Circular Orbit Patterns Providing Continuous Whole Earth Coverage*,
/// Royal Aircraft Establishment Technical Report 70211, 1970.
///
/// 把该构型实例化为一组轨道根数。
///
/// 第一个轨道面的参考卫星的平近点角为零。同一面内每增加一颗卫星,平近点角
/// 前进 `360 / (t / p)` 度;每增加一个轨道面,前进一个相位偏移。
///
/// 当该构型无法实例化时返回一个说明原因的错误。
///
/// 出处:相位规则取自 Walker, J. G., *Circular Orbit Patterns Providing
/// Continuous Whole Earth Coverage*, Royal Aircraft Establishment
/// Technical Report 70211, 1970。
pub fn instantiate(self) -> Result<Vec<KeplerianElements>, ConstellationError> {
if self.total_satellites == 0 {
return Err(ConstellationError::new(
ConstellationErrorKind::NoSatellites,
));
}
if self.planes == 0 {
return Err(ConstellationError::new(ConstellationErrorKind::NoPlanes));
}
let per_plane = self.satellites_per_plane().ok_or_else(|| {
ConstellationError::new(ConstellationErrorKind::UnevenPlanes).with_detail(format!(
"{} satellites cannot be spread over {} planes",
self.total_satellites, self.planes
))
})?;
if !self.is_valid() {
return Err(
ConstellationError::new(ConstellationErrorKind::InvalidOrbit).with_detail(format!(
"inclination {} rad, semi-major axis {} km, phase factor {}",
self.inclination, self.semi_major_axis, self.phase_factor
)),
);
}
let plane_spacing = self.plane_spacing().expect("a positive plane count");
let phase_offset = self.phase_offset().expect("a positive satellite count");
let anomaly_spacing = 2.0 * PI / per_plane as f64;
let mut satellites = Vec::with_capacity(self.total_satellites);
for plane in 0..self.planes {
// The right ascension of this plane.
// 本轨道面的赤经。
let right_ascension = (plane as f64 * plane_spacing).rem_euclid(2.0 * PI);
// The phase offset of this plane relative to the first.
// 本轨道面相对第一个轨道面的相位偏移。
let plane_phase = plane as f64 * phase_offset;
for satellite in 0..per_plane {
let mean_anomaly =
(satellite as f64 * anomaly_spacing + plane_phase).rem_euclid(2.0 * PI);
satellites.push(KeplerianElements {
semi_major_axis: self.semi_major_axis,
// The Walker pattern is circular.
// Walker 构型为圆轨道。
eccentricity: 0.0,
inclination: self.inclination,
right_ascension,
// The argument of perigee is undefined for a circular
// orbit, so it is set to zero and the position along the
// orbit is carried entirely by the mean anomaly.
// 圆轨道的近地点幅角未定义,故置零;轨道上的位置完全由
// 平近点角承载。
argument_of_perigee: 0.0,
true_anomaly: apsis_kepler::true_from_mean(mean_anomaly, 0.0),
});
}
}
Ok(satellites)
}
}
/// One satellite in an instantiated constellation.
///
/// 已实例化星座中的一颗卫星。
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Satellite {
/// The index of the satellite within the constellation.
///
/// 该卫星在星座中的序号。
pub index: usize,
/// The index of the orbital plane it belongs to.
///
/// 该卫星所属轨道面的序号。
pub plane: usize,
/// The index of the satellite within its plane.
///
/// 该卫星在其所在轨道面内的序号。
pub index_in_plane: usize,
/// The orbital elements at the reference time.
///
/// 历元时刻的轨道根数。
pub elements: KeplerianElements,
}
/// A constellation instantiated at a reference time.
///
/// The elements are the ones at the reference time passed to the
/// constructor. Advancing them in time is the job of the orbit layer, which
/// propagates each satellite independently.
///
/// 在参考时刻实例化的星座。
///
/// 根数即构造函数所给参考时刻的那一组。随时间推进由 orbit 层负责,该层对每颗
/// 卫星独立传播。
#[derive(Debug, Clone, PartialEq)]
pub struct Constellation {
satellites: Vec<Satellite>,
pattern: Option<WalkerPattern>,
reference_mjd: f64,
}
impl Constellation {
/// Instantiates a Walker pattern at a reference time.
///
/// The reference time is a Modified Julian Date on the TAI scale. It is
/// stored with the constellation and returned unchanged; the elements
/// are the ones at that instant.
///
/// Returns an error when the pattern cannot be instantiated.
///
/// 在参考时刻实例化一个 Walker 构型。
///
/// 参考时刻为 TAI 时间尺度上的修正儒略日(Modified Julian Date)。它随
/// 星座一并保存并原样返回;根数即该时刻的根数。
///
/// 当该构型无法实例化时返回一个错误。
pub fn walker(pattern: WalkerPattern, reference_mjd: f64) -> Result<Self, ConstellationError> {
if !reference_mjd.is_finite() {
return Err(
ConstellationError::new(ConstellationErrorKind::InvalidOrbit)
.with_detail("the reference time is not finite"),
);
}
let elements = pattern.instantiate()?;
let per_plane = pattern
.satellites_per_plane()
.expect("instantiation checked the division");
let satellites = elements
.into_iter()
.enumerate()
.map(|(index, elements)| Satellite {
// The satellites are produced plane by plane, so the plane
// index is the quotient and the position within it the
// remainder.
// 卫星按轨道面依次产生,故轨道面序号为商,面内序号为余数。
index,
plane: index / per_plane,
index_in_plane: index % per_plane,
elements,
})
.collect();
Ok(Self {
satellites,
pattern: Some(pattern),
reference_mjd,
})
}
/// Builds a constellation from a list of satellites.
///
/// This is the constructor for a constellation that does not follow the
/// Walker pattern. The plane index of each satellite is derived from the
/// right ascension of its elements, so satellites sharing a plane are
/// grouped.
///
/// Returns an error when the list is empty, when the reference time is
/// not finite, or when any element set is not a valid orbit.
///
/// 由一组卫星根数构建星座。
///
/// 这是为不遵循 Walker 构型的星座提供的构造函数。每颗卫星的轨道面序号由其根数
/// 的赤经推得,因此共用同一轨道面的卫星会被归为一组。
///
/// 当列表为空、参考时刻非有限,或任一根数集不是有效轨道时返回一个错误。
pub fn from_satellites(
elements: &[KeplerianElements],
reference_mjd: f64,
) -> Result<Self, ConstellationError> {
if elements.is_empty() {
return Err(ConstellationError::new(
ConstellationErrorKind::NoSatellites,
));
}
if !reference_mjd.is_finite() {
return Err(
ConstellationError::new(ConstellationErrorKind::InvalidOrbit)
.with_detail("the reference time is not finite"),
);
}
// Group by right ascension, assigning plane indices in order of
// first appearance so that the numbering is stable.
// 按赤经分组,轨道面序号按首次出现的顺序分配,使编号稳定。
let mut planes: Vec<f64> = Vec::new();
let mut satellites = Vec::with_capacity(elements.len());
let mut counts: Vec<usize> = Vec::new();
for (index, elements) in elements.iter().enumerate() {
if !elements.is_valid() {
return Err(
ConstellationError::new(ConstellationErrorKind::InvalidOrbit)
.with_detail(format!("satellite {index} has invalid elements")),
);
}
let plane = match planes
.iter()
.position(|value| (value - elements.right_ascension).abs() < 1e-12)
{
Some(plane) => plane,
None => {
planes.push(elements.right_ascension);
counts.push(0);
planes.len() - 1
}
};
let index_in_plane = counts[plane];
counts[plane] += 1;
satellites.push(Satellite {
index,
plane,
index_in_plane,
elements: *elements,
});
}
Ok(Self {
satellites,
pattern: None,
reference_mjd,
})
}
/// Returns the satellites in the constellation.
///
/// 返回星座中的卫星。
pub fn satellites(&self) -> &[Satellite] {
&self.satellites
}
/// Returns the number of satellites.
///
/// 返回卫星数。
pub fn len(&self) -> usize {
self.satellites.len()
}
/// Returns `true` when the constellation is empty.
///
/// A constellation built through either constructor is never empty, so
/// this always returns `false`; it exists because a collection type
/// should offer it alongside [`Constellation::len`].
///
/// 经任一构造函数构建的星座都不会为空,故此函数恒返回 `false`;它存在是因为
/// 集合类型应当与 [`Constellation::len`] 一并提供。
pub fn is_empty(&self) -> bool {
self.satellites.is_empty()
}
/// Returns the number of orbital planes.
///
/// 返回轨道面数。
pub fn plane_count(&self) -> usize {
self.satellites
.iter()
.map(|satellite| satellite.plane)
.max()
.map_or(0, |highest| highest + 1)
}
/// Returns the Walker pattern, when the constellation was built from
/// one.
///
/// 若星座由 Walker 构型构建则返回该构型,否则返回 `None`。
pub fn pattern(&self) -> Option<WalkerPattern> {
self.pattern
}
/// Returns the reference time as a Modified Julian Date on the TAI
/// scale.
///
/// 返回参考时刻,为 TAI 时间尺度上的修正儒略日(Modified Julian Date)。
pub fn reference_mjd(&self) -> f64 {
self.reference_mjd
}
/// Returns the satellites of one plane.
///
/// Returns an empty slice when the plane index is out of range.
///
/// 返回某个轨道面上的卫星。
///
/// 当轨道面序号越界时返回空切片。
pub fn plane(&self, plane: usize) -> Vec<Satellite> {
self.satellites
.iter()
.filter(|satellite| satellite.plane == plane)
.copied()
.collect()
}
/// Returns the altitude of the constellation above the given radius, in
/// kilometers.
///
/// Every satellite of a Walker pattern shares a semi-major axis, so a
/// single altitude describes the whole constellation. For a custom
/// constellation this is the altitude of the first satellite.
///
/// Returns `None` when the constellation is empty.
///
/// Walker 构型中所有卫星共用同一半长轴,故单一高度即可描述整个星座;对自定义
/// 星座,返回的是第一颗卫星的高度。
///
/// 星座为空时返回 `None`。
pub fn altitude_above(&self, body_radius_km: f64) -> Option<f64> {
self.satellites
.first()
.map(|satellite| satellite.elements.semi_major_axis - body_radius_km)
}
/// Returns the count of satellites in each plane.
///
/// The result has one entry per plane, in plane order.
///
/// 返回的向量每个轨道面一项,按轨道面顺序排列。
pub fn plane_populations(&self) -> Vec<usize> {
let mut populations = vec![0; self.plane_count()];
for satellite in &self.satellites {
populations[satellite.plane] += 1;
}
populations
}
}