245 lines
9.5 KiB
Rust
245 lines
9.5 KiB
Rust
//! Initialisation of the propagator from a two-line element set.
|
||
//!
|
||
//! The element set gives mean elements in degrees, revolutions per day, and
|
||
//! a drag term. The propagator works in radians, radians per minute, and
|
||
//! Earth radii, so the initialisation converts the inputs and precomputes
|
||
//! every coefficient that depends only on the elements.
|
||
//!
|
||
//! The quantity `no_unkozai` is the mean motion after the correction that
|
||
//! removes the effect of the Kozai mean motion convention used in the
|
||
//! element set. The correction follows from the second zonal harmonic and
|
||
//! is solved by a fixed point iteration, because the corrected value appears
|
||
//! on both sides of the relation.
|
||
//!
|
||
//! Source: the correction and the coefficient definitions follow Vallado et
|
||
//! al., *Revisiting Spacetrack Report #3*, AIAA 2006-6753, 2006.
|
||
|
||
use crate::error::{PropagationError, PropagationErrorKind};
|
||
use crate::model::GravityModel;
|
||
use apsis_tle::TwoLineElement;
|
||
use core::f64::consts::PI;
|
||
|
||
/// The number of minutes in a day.
|
||
///
|
||
/// 一天中的分钟数。
|
||
const MINUTES_PER_DAY: f64 = 1_440.0;
|
||
|
||
/// The number of radians in a revolution.
|
||
///
|
||
/// 一周中的弧度数。
|
||
const TWO_PI: f64 = 2.0 * PI;
|
||
|
||
/// The period, in minutes, at or above which the deep space branch is used.
|
||
///
|
||
/// The theory states 225 minutes. Satellites below that period are handled
|
||
/// by the near-Earth branch, which is the case for all low Earth orbits.
|
||
///
|
||
/// 使用深空分支的周期阈值,单位为分钟。
|
||
///
|
||
/// 理论给出 225 分钟。周期短于该值的卫星由近地分支处理,所有低地球轨道皆属此类。
|
||
const DEEP_SPACE_PERIOD_MINUTES: f64 = 225.0;
|
||
|
||
/// The elements of a satellite in the units the propagator uses.
|
||
///
|
||
/// Angles are in radians and mean motion is in radians per minute. Helpers
|
||
/// convert a [`TwoLineElement`] into this form.
|
||
///
|
||
/// 卫星根数在传播器所用单位下的形式。
|
||
///
|
||
/// 角度用弧度,平运动用弧度每分钟。辅助函数把 [`TwoLineElement`] 转为该形式。
|
||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||
pub struct Elements {
|
||
/// Epoch of the elements, expressed as a Modified Julian Date on the
|
||
/// scale the element set was written in.
|
||
///
|
||
/// 根数历元,以根数集书写所用标度上的修正儒略日(Modified Julian Date)
|
||
/// 表示。
|
||
pub epoch_mjd: f64,
|
||
/// `BSTAR` drag term, in inverse Earth radii.
|
||
///
|
||
/// `BSTAR` 阻力项,单位为地球半径的倒数。
|
||
pub bstar: f64,
|
||
/// Mean motion, in radians per minute.
|
||
///
|
||
/// 平运动,单位为弧度每分钟。
|
||
pub mean_motion: f64,
|
||
/// Eccentricity, dimensionless.
|
||
///
|
||
/// 偏心率,无量纲。
|
||
pub eccentricity: f64,
|
||
/// Inclination, in radians.
|
||
///
|
||
/// 倾角,单位为弧度。
|
||
pub inclination: f64,
|
||
/// Right ascension of the ascending node, in radians.
|
||
///
|
||
/// 升交点赤经,单位为弧度。
|
||
pub right_ascension: f64,
|
||
/// Argument of perigee, in radians.
|
||
///
|
||
/// 近地点幅角,单位为弧度。
|
||
pub argument_of_perigee: f64,
|
||
/// Mean anomaly, in radians.
|
||
///
|
||
/// 平近点角,单位为弧度。
|
||
pub mean_anomaly: f64,
|
||
}
|
||
|
||
impl Elements {
|
||
/// Converts a parsed element set into the units the propagator uses.
|
||
///
|
||
/// The epoch is taken as the UTC Modified Julian Date exactly as written
|
||
/// in the element set. The element set epoch is a UTC calendar instant,
|
||
/// but the propagator treats it as a uniform time origin, which is the
|
||
/// convention the theory was fitted under. Converting it to a uniform
|
||
/// scale first would shift the epoch by the leap second offset, about
|
||
/// 33 seconds, and that shift propagates into the solar and lunar series
|
||
/// of the deep space branch.
|
||
///
|
||
/// 把已解析的根数集转换为传播器所用单位。
|
||
///
|
||
/// 历元直接取根数集中原样写下的 UTC 修正儒略日(Modified Julian Date)。
|
||
/// 根数集历元是 UTC 日历瞬时,但传播器把它当作均匀时间原点,这正是理论
|
||
/// 拟合所依据的约定。若先换算到均匀标度,历元会偏移一个闰秒量(约 33
|
||
/// 秒),而该偏移会传播进深空分支的日月级数。
|
||
pub fn from_tle(element: &TwoLineElement) -> Option<Self> {
|
||
let epoch_mjd = element.epoch.mjd(apsis_time::TimeScale::Utc)?;
|
||
Some(Self {
|
||
epoch_mjd,
|
||
bstar: element.bstar,
|
||
mean_motion: element.mean_motion * TWO_PI / MINUTES_PER_DAY,
|
||
eccentricity: element.eccentricity,
|
||
inclination: element.inclination.to_radians(),
|
||
right_ascension: element.right_ascension.to_radians(),
|
||
argument_of_perigee: element.argument_of_perigee.to_radians(),
|
||
mean_anomaly: element.mean_anomaly.to_radians(),
|
||
})
|
||
}
|
||
}
|
||
|
||
/// Applies the Kozai to Brouwer mean motion correction.
|
||
///
|
||
/// The mean motion in an element set is a Kozai mean motion, which the
|
||
/// theory must convert to a Brouwer mean motion before use. The relation is
|
||
///
|
||
/// ```text
|
||
/// n0 = n / (1 + 1.5 k2 (3 cos^2 i - 1) / (a^2 sqrt(1 - e^2)) / a^2)
|
||
/// ```
|
||
///
|
||
/// written in the form used here as a fixed point iteration, because `a`
|
||
/// depends on `n0` through Kepler's third law. The iteration converges from
|
||
/// the unmodified mean motion in a few steps for every orbit that appears in
|
||
/// an element set.
|
||
/// Applies the Kozai to Brouwer mean motion correction.
|
||
///
|
||
/// The mean motion in an element set is a Kozai mean motion, which the
|
||
/// theory must convert to a Brouwer mean motion before use. Writing `no` for
|
||
/// the element set value and `xke` for the Kepler constant, the reference
|
||
/// implementation evaluates
|
||
///
|
||
/// ```text
|
||
/// ak = (xke / no)^(2/3)
|
||
/// d1 = 0.75 j2 (3 cos^2 i - 1) / (sqrt(1 - e^2) (1 - e^2))
|
||
/// del = d1 / ak^2
|
||
/// adel = ak (1 - del^2 - del (1/3 + 134 del^2 / 81))
|
||
/// del = d1 / adel^2
|
||
/// n0 = no / (1 + del)
|
||
/// ```
|
||
///
|
||
/// The second evaluation of `del` uses `adel` rather than `ak`, which is
|
||
/// what makes the correction consistent with the semi-major axis implied by
|
||
/// the corrected mean motion.
|
||
///
|
||
/// Source: Vallado et al., *Revisiting Spacetrack Report #3*, AIAA
|
||
/// 2006-6753, 2006.
|
||
///
|
||
/// 施加从 Kozai 平运动到 Brouwer 平运动的修正。
|
||
///
|
||
/// 根数集中的平运动是 Kozai 平运动,理论须先把它换算为 Brouwer 平运动方能
|
||
/// 使用。以 `no` 表示根数集给出的值、`xke` 表示开普勒常数,参考实现按下式
|
||
/// 求值:
|
||
///
|
||
/// ```text
|
||
/// ak = (xke / no)^(2/3)
|
||
/// d1 = 0.75 j2 (3 cos^2 i - 1) / (sqrt(1 - e^2) (1 - e^2))
|
||
/// del = d1 / ak^2
|
||
/// adel = ak (1 - del^2 - del (1/3 + 134 del^2 / 81))
|
||
/// del = d1 / adel^2
|
||
/// n0 = no / (1 + del)
|
||
/// ```
|
||
///
|
||
/// 第二次求 `del` 用的是 `adel` 而非 `ak`,这正是该修正与修正后平运动所隐含
|
||
/// 的半长轴相一致的原因。
|
||
///
|
||
/// 出处:Vallado 等,*Revisiting Spacetrack Report #3*,AIAA 2006-6753,2006。
|
||
pub fn unkozai_mean_motion(
|
||
mean_motion: f64,
|
||
eccentricity: f64,
|
||
inclination: f64,
|
||
model: GravityModel,
|
||
) -> f64 {
|
||
let xke = model.xke();
|
||
let j2 = model.j2;
|
||
let cosio = inclination.cos();
|
||
let cosio2 = cosio * cosio;
|
||
let omeosq = 1.0 - eccentricity * eccentricity;
|
||
let rteosq = omeosq.sqrt();
|
||
|
||
let ak = (xke / mean_motion).powf(2.0 / 3.0);
|
||
let d1 = 0.75 * j2 * (3.0 * cosio2 - 1.0) / (rteosq * omeosq);
|
||
let mut delta = d1 / (ak * ak);
|
||
let adel = ak * (1.0 - delta * delta - delta * (1.0 / 3.0 + 134.0 * delta * delta / 81.0));
|
||
delta = d1 / (adel * adel);
|
||
mean_motion / (1.0 + delta)
|
||
}
|
||
|
||
/// Validates the mean elements before initialisation.
|
||
///
|
||
/// Returns an error when the orbit cannot be propagated, for example when
|
||
/// the eccentricity does not describe a closed orbit or the mean motion is
|
||
/// not positive.
|
||
///
|
||
/// 在初始化前校验平均根数。
|
||
///
|
||
/// 当轨道不可传播时返回错误,例如偏心率不描述闭合轨道,或平运动不为正。
|
||
pub fn validate(elements: &Elements) -> Result<(), PropagationError> {
|
||
if !elements.mean_motion.is_finite() || elements.mean_motion <= 0.0 {
|
||
return Err(PropagationError::new(PropagationErrorKind::InvalidElements)
|
||
.with_detail(format!("mean motion is {}", elements.mean_motion)));
|
||
}
|
||
if !elements.eccentricity.is_finite()
|
||
|| elements.eccentricity < 0.0
|
||
|| elements.eccentricity >= 1.0
|
||
{
|
||
return Err(PropagationError::new(PropagationErrorKind::InvalidElements)
|
||
.with_detail(format!("eccentricity is {}", elements.eccentricity)));
|
||
}
|
||
for (name, value) in [
|
||
("inclination", elements.inclination),
|
||
("right ascension", elements.right_ascension),
|
||
("argument of perigee", elements.argument_of_perigee),
|
||
("mean anomaly", elements.mean_anomaly),
|
||
] {
|
||
if !value.is_finite() {
|
||
return Err(PropagationError::new(PropagationErrorKind::InvalidElements)
|
||
.with_detail(format!("{name} is {value}")));
|
||
}
|
||
}
|
||
if !elements.bstar.is_finite() {
|
||
return Err(PropagationError::new(PropagationErrorKind::InvalidElements)
|
||
.with_detail(format!("bstar is {}", elements.bstar)));
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// Returns `true` when the elements describe a deep space orbit.
|
||
///
|
||
/// The test is on the orbital period, which follows from the mean motion.
|
||
///
|
||
/// 当这些根数描述一条深空轨道时返回 `true`。
|
||
///
|
||
/// 判据是轨道周期,它由平运动推出。
|
||
pub fn is_deep_space(mean_motion: f64) -> bool {
|
||
TWO_PI / mean_motion >= DEEP_SPACE_PERIOD_MINUTES
|
||
}
|