docs: bilingual comments for the foundation, physics and orbit layers
Applies the glossary across apsis-math, apsis-time, apsis-leap, apsis-coord, apsis-geodesy and the five orbit crates that were already implemented, plus apsis-ephemeris, apsis-maneuver and apsis-orbit. Five hundred and seven comment blocks now carry the English paragraph and the Chinese one. The checker learned to skip a comment that is only a formula, since a bare symbol has no prose to translate and repeating it in Chinese would be noise. The SGP4 cross-check still passes, so nothing in the numerical path moved.
This commit is contained in:
parent
64114251fa
commit
5c11b60069
|
|
@ -8,18 +8,31 @@ use apsis_math::Vec3;
|
|||
/// A sample is a position and velocity at a time, which is the form every
|
||||
/// propagator in the workspace produces and the form the interpolation
|
||||
/// needs.
|
||||
///
|
||||
/// 一个采样状态。
|
||||
///
|
||||
/// 样本是某时刻的位置与速度,这既是工作区中每个传播器产出的形式,也是插值所需
|
||||
/// 的形式。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Sample {
|
||||
/// The time of the sample, in seconds from the caller's origin.
|
||||
///
|
||||
/// 样本时刻,单位为自调用方原点起的秒数。
|
||||
pub seconds: f64,
|
||||
/// Position, in kilometers.
|
||||
///
|
||||
/// 位置,单位为千米。
|
||||
pub position: Vec3,
|
||||
/// Velocity, in kilometers per second.
|
||||
///
|
||||
/// 速度,单位为千米每秒。
|
||||
pub velocity: Vec3,
|
||||
}
|
||||
|
||||
impl Sample {
|
||||
/// Creates a sample.
|
||||
///
|
||||
/// 创建一个样本。
|
||||
pub const fn new(seconds: f64, position: Vec3, velocity: Vec3) -> Self {
|
||||
Self {
|
||||
seconds,
|
||||
|
|
@ -29,16 +42,22 @@ impl Sample {
|
|||
}
|
||||
|
||||
/// Returns `true` when the time, position, and velocity are finite.
|
||||
///
|
||||
/// 当时间、位置与速度都有限时返回 `true`。
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.seconds.is_finite() && self.position.is_finite() && self.velocity.is_finite()
|
||||
}
|
||||
|
||||
/// Returns the distance from the origin, in kilometers.
|
||||
///
|
||||
/// 返回到原点的距离,单位为千米。
|
||||
pub fn radius(&self) -> f64 {
|
||||
self.position.norm()
|
||||
}
|
||||
|
||||
/// Returns the speed, in kilometers per second.
|
||||
///
|
||||
/// 返回速率,单位为千米每秒。
|
||||
pub fn speed(&self) -> f64 {
|
||||
self.velocity.norm()
|
||||
}
|
||||
|
|
@ -54,6 +73,14 @@ impl Sample {
|
|||
/// carry the endpoints, the second and fourth the slopes. The derivative
|
||||
/// terms are scaled by `dt` because the slopes are per unit time while `s`
|
||||
/// is per unit interval.
|
||||
///
|
||||
/// 对一个分量求 Hermite 三次式的值。
|
||||
///
|
||||
/// `p0` 与 `p1` 为端点值,`v0` 与 `v1` 为端点处对时间的导数,`dt` 为区间长度,
|
||||
/// `s` 为区间内位置比例,位于 `[0, 1]`。
|
||||
///
|
||||
/// 四项即标准三次 Hermite 基:第一、三项承载端点值,第二、四项承载斜率。导数项
|
||||
/// 乘以 `dt`,因为斜率以单位时间计而 `s` 以单位区间计。
|
||||
pub fn hermite_position(p0: Vec3, v0: Vec3, p1: Vec3, v1: Vec3, dt: f64, s: f64) -> Vec3 {
|
||||
let s2 = s * s;
|
||||
let s3 = s2 * s;
|
||||
|
|
@ -69,6 +96,11 @@ pub fn hermite_position(p0: Vec3, v0: Vec3, p1: Vec3, v1: Vec3, dt: f64, s: f64)
|
|||
/// This is the velocity that goes with [`hermite_position`]. It is the exact
|
||||
/// derivative of that cubic, so the reconstructed state is internally
|
||||
/// consistent: a caller that differentiates the position gets this velocity.
|
||||
///
|
||||
/// 对一个分量求 Hermite 三次式的导数。
|
||||
///
|
||||
/// 这是与 [`hermite_position`] 配对的速度,且是该三次式的精确导数,因此重建
|
||||
/// 的状态内部自洽:调用方对位置求导会得到这个速度。
|
||||
pub fn hermite_velocity(p0: Vec3, v0: Vec3, p1: Vec3, v1: Vec3, dt: f64, s: f64) -> Vec3 {
|
||||
let s2 = s * s;
|
||||
let dh00 = 6.0 * s2 - 6.0 * s;
|
||||
|
|
@ -78,10 +110,14 @@ pub fn hermite_velocity(p0: Vec3, v0: Vec3, p1: Vec3, v1: Vec3, dt: f64, s: f64)
|
|||
// The chain rule turns the per interval derivative into a per second
|
||||
// one, which divides the position terms by `dt` and leaves the velocity
|
||||
// terms, already per second, untouched.
|
||||
// 链式法则把每单位区间的导数变为每单位秒的导数:位置项除以 `dt`,而速度项
|
||||
// 本就以每秒计,保持不变。
|
||||
p0 * (dh00 / dt) + v0 * dh10 + p1 * (dh01 / dt) + v1 * dh11
|
||||
}
|
||||
|
||||
/// Evaluates both the Hermite position and velocity.
|
||||
///
|
||||
/// 同时求 Hermite 位置与速度。
|
||||
pub fn hermite_state(p0: Vec3, v0: Vec3, p1: Vec3, v1: Vec3, dt: f64, s: f64) -> (Vec3, Vec3) {
|
||||
(
|
||||
hermite_position(p0, v0, p1, v1, dt, s),
|
||||
|
|
@ -97,6 +133,12 @@ pub fn hermite_state(p0: Vec3, v0: Vec3, p1: Vec3, v1: Vec3, dt: f64, s: f64) ->
|
|||
/// query outside the covered interval is refused rather than extrapolated,
|
||||
/// because the interpolation has no information there and an extrapolated
|
||||
/// orbit can diverge quickly.
|
||||
///
|
||||
/// 以插值回答查询的采样状态表。
|
||||
///
|
||||
/// 该表至少含两个样本,时间严格递增。在样本时刻查询会精确返回该样本;两个样本
|
||||
/// 之间的查询返回穿过四个端点值的 Hermite 三次式。落在覆盖区间之外的查询会被
|
||||
/// 拒绝而非外推,因为插值在那里没有信息,而外推的轨道可能迅速发散。
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Ephemeris {
|
||||
samples: Vec<Sample>,
|
||||
|
|
@ -107,6 +149,10 @@ impl Ephemeris {
|
|||
///
|
||||
/// Returns an error when there are fewer than two samples, when a sample
|
||||
/// is not finite, or when the times do not strictly increase.
|
||||
///
|
||||
/// 由时间递增的样本构建该表。
|
||||
///
|
||||
/// 当样本少于两个、某个样本非有限,或时间未严格递增时返回错误。
|
||||
pub fn from_samples(samples: Vec<Sample>) -> Result<Self, EphemerisError> {
|
||||
if samples.len() < 2 {
|
||||
return Err(EphemerisError::new(EphemerisErrorKind::TooFewSamples)
|
||||
|
|
@ -132,11 +178,15 @@ impl Ephemeris {
|
|||
}
|
||||
|
||||
/// Returns the samples in time order.
|
||||
///
|
||||
/// 返回按时间排列的样本。
|
||||
pub fn samples(&self) -> &[Sample] {
|
||||
&self.samples
|
||||
}
|
||||
|
||||
/// Returns the number of samples.
|
||||
///
|
||||
/// 返回样本数。
|
||||
pub fn len(&self) -> usize {
|
||||
self.samples.len()
|
||||
}
|
||||
|
|
@ -146,21 +196,32 @@ impl Ephemeris {
|
|||
/// A table built through [`Ephemeris::from_samples`] is never empty, so
|
||||
/// this is always `false` for one that exists; it is provided so that
|
||||
/// generic code that accepts a slice can use the usual pair of methods.
|
||||
///
|
||||
/// 当该表为空时返回 `true`。
|
||||
///
|
||||
/// 经 [`Ephemeris::from_samples`] 构建的表不会为空,故已存在的表恒返回
|
||||
/// `false`;提供它是为了让接受切片的泛型代码可以配上惯常的一对方法。
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.samples.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the first sample time, in seconds.
|
||||
///
|
||||
/// 返回首个样本时刻,单位为秒。
|
||||
pub fn start_time(&self) -> f64 {
|
||||
self.samples.first().map_or(0.0, |sample| sample.seconds)
|
||||
}
|
||||
|
||||
/// Returns the last sample time, in seconds.
|
||||
///
|
||||
/// 返回末个样本时刻,单位为秒。
|
||||
pub fn end_time(&self) -> f64 {
|
||||
self.samples.last().map_or(0.0, |sample| sample.seconds)
|
||||
}
|
||||
|
||||
/// Returns the time the table covers, in seconds.
|
||||
///
|
||||
/// 返回该表覆盖的时间,单位为秒。
|
||||
pub fn duration(&self) -> f64 {
|
||||
self.end_time() - self.start_time()
|
||||
}
|
||||
|
|
@ -168,6 +229,10 @@ impl Ephemeris {
|
|||
/// Returns `true` when a time falls within the covered interval.
|
||||
///
|
||||
/// The interval is closed, so its endpoints are covered.
|
||||
///
|
||||
/// 当某时刻落在覆盖区间内时返回 `true`。
|
||||
///
|
||||
/// 该区间是闭的,故其端点也被覆盖。
|
||||
pub fn covers(&self, seconds: f64) -> bool {
|
||||
seconds.is_finite() && seconds >= self.start_time() && seconds <= self.end_time()
|
||||
}
|
||||
|
|
@ -176,6 +241,10 @@ impl Ephemeris {
|
|||
///
|
||||
/// Returns `None` when the spacing varies, which a caller checks before
|
||||
/// indexing samples by arithmetic.
|
||||
///
|
||||
/// 当样本等间隔时返回标称步长。
|
||||
///
|
||||
/// 间隔不一致时返回 `None`;调用方应先检查这一点,再按算术方式索引样本。
|
||||
pub fn uniform_step(&self) -> Option<f64> {
|
||||
let first = self.samples.windows(2).next()?[1].seconds - self.samples[0].seconds;
|
||||
let uniform = self
|
||||
|
|
@ -186,6 +255,8 @@ impl Ephemeris {
|
|||
}
|
||||
|
||||
/// Returns `true` when the samples are evenly spaced.
|
||||
///
|
||||
/// 当样本等间隔时返回 `true`。
|
||||
pub fn is_uniform(&self) -> bool {
|
||||
self.uniform_step().is_some()
|
||||
}
|
||||
|
|
@ -194,6 +265,11 @@ impl Ephemeris {
|
|||
///
|
||||
/// The index `i` is such that `samples[i].seconds <= seconds <=
|
||||
/// samples[i + 1].seconds`. A time outside the interval returns `None`.
|
||||
///
|
||||
/// 返回包含某时刻的区间下标。
|
||||
///
|
||||
/// 下标 `i` 满足 `samples[i].seconds <= seconds <= samples[i + 1].seconds`。
|
||||
/// 落在该区间之外的时刻返回 `None`。
|
||||
fn interval_of(&self, seconds: f64) -> Option<usize> {
|
||||
if !self.covers(seconds) {
|
||||
return None;
|
||||
|
|
@ -201,6 +277,8 @@ impl Ephemeris {
|
|||
// A binary search for the last sample at or before the time. The
|
||||
// partition point is the first sample strictly after it, so the
|
||||
// interval starts one before, clamped to the last interval.
|
||||
// 二分查找最后一个不晚于该时刻的样本。分区点是其后第一个严格靠后的样本,
|
||||
// 故区间起点为其前一,并钳位到最后一个区间。
|
||||
let after = self
|
||||
.samples
|
||||
.partition_point(|sample| sample.seconds <= seconds);
|
||||
|
|
@ -212,6 +290,10 @@ impl Ephemeris {
|
|||
///
|
||||
/// A time equal to a sample time returns that sample. Returns `None`
|
||||
/// when the time is outside the covered interval.
|
||||
///
|
||||
/// 返回某时刻的状态,在样本之间插值。
|
||||
///
|
||||
/// 时刻与某样本相同时返回该样本。落在覆盖区间之外时返回 `None`。
|
||||
pub fn state_at(&self, seconds: f64) -> Option<Sample> {
|
||||
let index = self.interval_of(seconds)?;
|
||||
let first = self.samples[index];
|
||||
|
|
@ -219,6 +301,8 @@ impl Ephemeris {
|
|||
|
||||
// At a sample the interpolation must reproduce the sample exactly,
|
||||
// which avoids the round off of evaluating the cubic at its end.
|
||||
// 在样本处插值必须精确复现该样本,从而避免在三次式端点求值带来的
|
||||
// 舍入误差。
|
||||
if seconds == first.seconds {
|
||||
return Some(first);
|
||||
}
|
||||
|
|
@ -244,6 +328,10 @@ impl Ephemeris {
|
|||
///
|
||||
/// This is for a caller that must produce a value for any time and is
|
||||
/// content to hold the last known state rather than fail.
|
||||
///
|
||||
/// 返回某时刻的状态;时刻落在覆盖区间之外时返回最近的端点。
|
||||
///
|
||||
/// 适用于必须对任意时刻给出取值、且宁可保持最近已知状态而不愿失败的调用方。
|
||||
pub fn clamped_state_at(&self, seconds: f64) -> Option<Sample> {
|
||||
if self.covers(seconds) {
|
||||
return self.state_at(seconds);
|
||||
|
|
@ -259,6 +347,8 @@ impl Ephemeris {
|
|||
}
|
||||
|
||||
/// Returns the sample nearest to a time, without interpolating.
|
||||
///
|
||||
/// 返回距某时刻最近的样本,不作插值。
|
||||
pub fn nearest_sample(&self, seconds: f64) -> Option<Sample> {
|
||||
if !seconds.is_finite() {
|
||||
return None;
|
||||
|
|
@ -280,6 +370,8 @@ impl Ephemeris {
|
|||
}
|
||||
|
||||
/// Returns the times of every sample, in order.
|
||||
///
|
||||
/// 返回各样本时刻,按顺序排列。
|
||||
pub fn times(&self) -> Vec<f64> {
|
||||
self.samples.iter().map(|sample| sample.seconds).collect()
|
||||
}
|
||||
|
|
@ -288,6 +380,11 @@ impl Ephemeris {
|
|||
///
|
||||
/// This is a convenience for a caller that wants to check a property of
|
||||
/// the whole table, such as every radius being above the surface, once.
|
||||
///
|
||||
/// 当每个状态都满足给定谓词时返回 `true`。
|
||||
///
|
||||
/// 这是给需要一次性检查整表性质的调用方的便利方法,例如检查所有半径都在
|
||||
/// 表面之上。
|
||||
pub fn all_samples<F>(&self, predicate: F) -> bool
|
||||
where
|
||||
F: Fn(&Sample) -> bool,
|
||||
|
|
|
|||
|
|
@ -3,20 +3,34 @@
|
|||
use core::fmt;
|
||||
|
||||
/// The category of an ephemeris error.
|
||||
///
|
||||
/// 星历错误的类别。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum EphemerisErrorKind {
|
||||
/// The sampling grid has fewer than two samples.
|
||||
///
|
||||
/// 采样网格少于两个样本。
|
||||
TooFewSamples,
|
||||
/// A time or a step is not finite.
|
||||
///
|
||||
/// 某时刻或步长非有限。
|
||||
NotFinite,
|
||||
/// A step is zero or negative.
|
||||
///
|
||||
/// 步长为零或为负。
|
||||
NonPositiveStep,
|
||||
/// The samples are not in increasing time order.
|
||||
///
|
||||
/// 样本未按时间递增排列。
|
||||
NotMonotonic,
|
||||
/// A state produced by the sampler is not finite.
|
||||
///
|
||||
/// 采样器产生的状态非有限。
|
||||
InvalidState,
|
||||
/// A query time falls outside the covered interval.
|
||||
///
|
||||
/// 查询时刻落在覆盖区间之外。
|
||||
OutOfRange,
|
||||
}
|
||||
|
||||
|
|
@ -35,6 +49,8 @@ impl fmt::Display for EphemerisErrorKind {
|
|||
}
|
||||
|
||||
/// A failure while building or querying an ephemeris.
|
||||
///
|
||||
/// 构建或查询星历时发生的失败。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EphemerisError {
|
||||
kind: EphemerisErrorKind,
|
||||
|
|
@ -43,22 +59,30 @@ pub struct EphemerisError {
|
|||
|
||||
impl EphemerisError {
|
||||
/// Creates an error with the given category.
|
||||
///
|
||||
/// 用给定类别创建一个错误。
|
||||
pub fn new(kind: EphemerisErrorKind) -> Self {
|
||||
Self { kind, detail: None }
|
||||
}
|
||||
|
||||
/// Attaches a description of the value that caused the failure.
|
||||
///
|
||||
/// 附加对该失败所涉数值的说明。
|
||||
pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
|
||||
self.detail = Some(detail.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the category of this failure.
|
||||
///
|
||||
/// 返回该失败的类别。
|
||||
pub const fn kind(&self) -> EphemerisErrorKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
/// Returns the detail text attached to this error, if any.
|
||||
///
|
||||
/// 返回附加在该错误上的说明文本,若有。
|
||||
pub fn detail(&self) -> Option<&str> {
|
||||
self.detail.as_deref()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,18 +12,33 @@ use apsis_math::Vec3;
|
|||
/// the end, with the end itself included whether or not it falls on the
|
||||
/// grid. Including the end matters, because a caller that asks for coverage
|
||||
/// up to a time expects the table to reach it.
|
||||
///
|
||||
/// 状态函数求值的均匀时间网格。
|
||||
///
|
||||
/// 采样器说明何时采样以及采样多密。网格由起始时刻、结束时刻与步长定义,样本数
|
||||
/// 随之而定:样本置于起点及此后每隔一步处,并始终包含结束时刻,无论它是否落在
|
||||
/// 网格上。包含结束时刻很重要,因为要求覆盖到某时刻的调用方期望表格确实延伸到
|
||||
/// 那里。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Sampler {
|
||||
/// The time of the first sample, in seconds.
|
||||
///
|
||||
/// 首个样本时刻,单位为秒。
|
||||
pub start_seconds: f64,
|
||||
/// The time of the last sample, in seconds.
|
||||
///
|
||||
/// 末个样本时刻,单位为秒。
|
||||
pub end_seconds: f64,
|
||||
/// The spacing between samples, in seconds.
|
||||
///
|
||||
/// 样本间隔,单位为秒。
|
||||
pub step_seconds: f64,
|
||||
}
|
||||
|
||||
impl Sampler {
|
||||
/// Creates a sampler over an interval with a step.
|
||||
///
|
||||
/// 按给定步长在某区间上创建采样器。
|
||||
pub const fn new(start_seconds: f64, end_seconds: f64, step_seconds: f64) -> Self {
|
||||
Self {
|
||||
start_seconds,
|
||||
|
|
@ -38,6 +53,11 @@ impl Sampler {
|
|||
/// and the step must be finite and positive. A zero length interval is
|
||||
/// refused because a table needs at least two distinct times and the
|
||||
/// interpolation has no interval to work over.
|
||||
///
|
||||
/// 当该采样器描述的网格可用时返回 `true`。
|
||||
///
|
||||
/// 各时刻须有限,结束时刻须严格晚于起始时刻,步长须有限且为正。零长度区间
|
||||
/// 会被拒绝,因为表格至少需要两个不同时刻,而插值没有区间可用。
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.start_seconds.is_finite()
|
||||
&& self.end_seconds.is_finite()
|
||||
|
|
@ -51,6 +71,11 @@ impl Sampler {
|
|||
/// This is the number of whole steps that fit in the interval plus one
|
||||
/// for the start and one for the end when the end is off the grid.
|
||||
/// Returns `None` when the sampler is not usable.
|
||||
///
|
||||
/// 返回该网格产生的样本数。
|
||||
///
|
||||
/// 它等于区间内容纳的整步数,加上起点那一个;当结束时刻不在网格上时再加
|
||||
/// 结束那一个。采样器不可用时返回 `None`。
|
||||
pub fn sample_count(&self) -> Option<usize> {
|
||||
if !self.is_valid() {
|
||||
return None;
|
||||
|
|
@ -59,6 +84,7 @@ impl Sampler {
|
|||
let whole_steps = (span / self.step_seconds).floor();
|
||||
// The end sample is added unless it already coincides with the last
|
||||
// whole step within a rounding tolerance.
|
||||
// 除非结束样本在舍入容差内已与最后一个整步重合,否则补上它。
|
||||
let remainder = span - whole_steps * self.step_seconds;
|
||||
let extra = if remainder > self.step_seconds * 1e-9 {
|
||||
1
|
||||
|
|
@ -73,6 +99,11 @@ impl Sampler {
|
|||
/// Returns `None` when the sampler is not usable. The last time is the
|
||||
/// end when the end does not fall on the grid, and the last grid point
|
||||
/// otherwise.
|
||||
///
|
||||
/// 返回网格上的各时刻,按顺序排列。
|
||||
///
|
||||
/// 采样器不可用时返回 `None`。结束时刻不落在网格上时最后一项为结束时刻,
|
||||
/// 否则为最后一个网格点。
|
||||
pub fn times(&self) -> Option<Vec<f64>> {
|
||||
let count = self.sample_count()?;
|
||||
let mut times = Vec::with_capacity(count);
|
||||
|
|
@ -84,6 +115,7 @@ impl Sampler {
|
|||
times.push(time);
|
||||
}
|
||||
// The end is always included, whether or not it landed on the grid.
|
||||
// 无论结束时刻是否落在网格上,它总被包含。
|
||||
if times.last().copied() != Some(self.end_seconds) {
|
||||
times.push(self.end_seconds);
|
||||
}
|
||||
|
|
@ -99,6 +131,14 @@ impl Sampler {
|
|||
///
|
||||
/// Returns an error when the sampler is not usable or when the function
|
||||
/// produces a state that is not finite.
|
||||
///
|
||||
/// 在网格上求值状态函数以填充星历。
|
||||
///
|
||||
/// 该函数把以秒计的时刻映射为位置与速度。在本工作区它通常是
|
||||
/// `apsis_sgp4::Satellite::propagate_at` 上的闭包,但任何函数都可以,因此
|
||||
/// 采样器也可针对解析轨道测试。
|
||||
///
|
||||
/// 采样器不可用或函数产生非有限状态时返回错误。
|
||||
pub fn sample<F>(&self, mut state_at: F) -> Result<Ephemeris, EphemerisError>
|
||||
where
|
||||
F: FnMut(f64) -> Sample,
|
||||
|
|
@ -124,6 +164,8 @@ impl Sampler {
|
|||
// The time is set from the grid rather than trusted from the
|
||||
// function, so the table stays strictly ordered even if the
|
||||
// function returns a slightly different time.
|
||||
// 时刻取自网格而不采信函数返回值,因此即便函数给出略有差异的时刻,
|
||||
// 表格仍保持严格有序。
|
||||
samples.push(Sample::new(time, sample.position, sample.velocity));
|
||||
}
|
||||
Ephemeris::from_samples(samples)
|
||||
|
|
@ -135,6 +177,11 @@ impl Sampler {
|
|||
/// central difference. This is for a position-only source, such as a
|
||||
/// tabulated file; where a velocity is available it should be used, so
|
||||
/// prefer [`Sampler::sample`].
|
||||
///
|
||||
/// 用只返回位置的函数填充星历。
|
||||
///
|
||||
/// 速度由相邻位置用中心差分重建。这适用于只有位置的数据源,例如制表文件;
|
||||
/// 若可得速度应予使用,故优先选用 [`Sampler::sample`]。
|
||||
pub fn sample_positions<F>(&self, mut position_at: F) -> Result<Ephemeris, EphemerisError>
|
||||
where
|
||||
F: FnMut(f64) -> Vec3,
|
||||
|
|
@ -158,6 +205,8 @@ impl Sampler {
|
|||
// A central difference inside the interval and a one sided one at
|
||||
// each end. The finite difference is second order, which matches the
|
||||
// accuracy of the Hermite interpolation it feeds.
|
||||
// 区间内部用中心差分,两端各用单边差分。该有限差分为二阶,与其所供给的
|
||||
// Hermite 插值精度相匹配。
|
||||
let count = positions.len();
|
||||
let mut samples = Vec::with_capacity(count);
|
||||
for (index, &position) in positions.iter().enumerate() {
|
||||
|
|
|
|||
|
|
@ -24,16 +24,23 @@
|
|||
use core::f64::consts::PI;
|
||||
|
||||
/// The maximum number of Newton iterations used to solve Kepler's equation.
|
||||
///
|
||||
/// 求解开普勒方程所用的 Newton 迭代最大步数。
|
||||
const MAX_ITERATIONS: usize = 30;
|
||||
|
||||
/// The convergence tolerance of the Newton iteration, in radians.
|
||||
///
|
||||
/// Newton 迭代的收敛容差,单位为弧度。
|
||||
const TOLERANCE: f64 = 1e-14;
|
||||
|
||||
/// Reduces an angle into `[-pi, pi)`.
|
||||
///
|
||||
/// 把角度归约到 `[-pi, pi)`。
|
||||
pub fn wrap_to_pi(angle: f64) -> f64 {
|
||||
let wrapped = (angle + PI).rem_euclid(2.0 * PI) - PI;
|
||||
// `rem_euclid` can return exactly pi for an input just below pi, which
|
||||
// the half-open interval excludes.
|
||||
// 对略小于 pi 的输入,`rem_euclid` 可能恰好返回 pi,而半开区间不含该端点。
|
||||
if wrapped >= PI {
|
||||
wrapped - 2.0 * PI
|
||||
} else {
|
||||
|
|
@ -52,6 +59,16 @@ pub fn wrap_to_pi(angle: f64) -> f64 {
|
|||
/// The half angle form is used rather than the direct cosine relation so
|
||||
/// that the result is correct in all four quadrants without a quadrant
|
||||
/// check.
|
||||
///
|
||||
/// 由偏近点角与偏心率返回真近点角。
|
||||
///
|
||||
/// 该关系来自辅助圆的几何性质:
|
||||
///
|
||||
/// ```text
|
||||
/// tan(nu / 2) = sqrt((1 + e) / (1 - e)) tan(E / 2)
|
||||
/// ```
|
||||
///
|
||||
/// 采用半角形式而非直接的余弦关系,使结果在四个象限中都正确,无需判别象限。
|
||||
pub fn true_from_eccentric(eccentric_anomaly: f64, eccentricity: f64) -> f64 {
|
||||
let half = eccentric_anomaly / 2.0;
|
||||
let factor = ((1.0 + eccentricity) / (1.0 - eccentricity)).sqrt();
|
||||
|
|
@ -61,6 +78,10 @@ pub fn true_from_eccentric(eccentric_anomaly: f64, eccentricity: f64) -> f64 {
|
|||
/// Returns the eccentric anomaly for a true anomaly and eccentricity.
|
||||
///
|
||||
/// This is the inverse of [`true_from_eccentric`].
|
||||
///
|
||||
/// 由真近点角与偏心率返回偏近点角。
|
||||
///
|
||||
/// 这是 [`true_from_eccentric`] 的逆运算。
|
||||
pub fn eccentric_from_true(true_anomaly: f64, eccentricity: f64) -> f64 {
|
||||
let half = true_anomaly / 2.0;
|
||||
let factor = ((1.0 - eccentricity) / (1.0 + eccentricity)).sqrt();
|
||||
|
|
@ -79,9 +100,21 @@ pub fn eccentric_from_true(true_anomaly: f64, eccentricity: f64) -> f64 {
|
|||
/// proportional to the eccentricity, which keeps the iteration stable for
|
||||
/// eccentricities approaching one. The iteration always terminates, and the
|
||||
/// result is the value reached after at most a fixed number of steps.
|
||||
///
|
||||
/// 由平近点角与偏心率返回偏近点角。
|
||||
///
|
||||
/// 用 Newton 法求解开普勒方程
|
||||
///
|
||||
/// ```text
|
||||
/// M = E - e sin E
|
||||
/// ```
|
||||
///
|
||||
/// 初值取平近点角加上一个正比于偏心率的项,使迭代在偏心率趋近一时保持稳定。
|
||||
/// 迭代总会终止,结果是至多固定步数后达到的值。
|
||||
pub fn eccentric_from_mean(mean_anomaly: f64, eccentricity: f64) -> f64 {
|
||||
let mean = wrap_to_pi(mean_anomaly);
|
||||
// A first order correction to the circular guess.
|
||||
// 对圆轨道猜测的一阶修正。
|
||||
let mut eccentric = mean + eccentricity * mean.sin() * (1.0 + eccentricity * mean.cos());
|
||||
for _ in 0..MAX_ITERATIONS {
|
||||
let residual = eccentric - eccentricity * eccentric.sin() - mean;
|
||||
|
|
@ -101,6 +134,10 @@ pub fn eccentric_from_mean(mean_anomaly: f64, eccentricity: f64) -> f64 {
|
|||
/// Returns the mean anomaly for an eccentric anomaly and eccentricity.
|
||||
///
|
||||
/// This is Kepler's equation evaluated directly, `M = E - e sin E`.
|
||||
///
|
||||
/// 由偏近点角与偏心率返回平近点角。
|
||||
///
|
||||
/// 这是直接求值的开普勒方程,`M = E - e sin E`。
|
||||
pub fn mean_from_eccentric(eccentric_anomaly: f64, eccentricity: f64) -> f64 {
|
||||
wrap_to_pi(eccentric_anomaly - eccentricity * eccentric_anomaly.sin())
|
||||
}
|
||||
|
|
@ -111,12 +148,19 @@ pub fn mean_from_eccentric(eccentric_anomaly: f64, eccentricity: f64) -> f64 {
|
|||
/// mean anomaly then follows from Kepler's equation. Going through the
|
||||
/// eccentric anomaly avoids the separate closed form for the swept area and
|
||||
/// keeps a single expression responsible for the relation.
|
||||
///
|
||||
/// 由真近点角与偏心率返回平近点角。
|
||||
///
|
||||
/// 先把真近点角转为偏近点角,再由开普勒方程得平近点角。绕经偏近点角避免了
|
||||
/// 另立扫过面积的闭式解,使该关系只由一个表达式负责。
|
||||
pub fn mean_from_true(true_anomaly: f64, eccentricity: f64) -> f64 {
|
||||
let eccentric_anomaly = eccentric_from_true(true_anomaly, eccentricity);
|
||||
mean_from_eccentric(eccentric_anomaly, eccentricity)
|
||||
}
|
||||
|
||||
/// Returns the true anomaly for a mean anomaly and eccentricity.
|
||||
///
|
||||
/// 由平近点角与偏心率返回真近点角。
|
||||
pub fn true_from_mean(mean_anomaly: f64, eccentricity: f64) -> f64 {
|
||||
true_from_eccentric(
|
||||
eccentric_from_mean(mean_anomaly, eccentricity),
|
||||
|
|
|
|||
|
|
@ -13,19 +13,38 @@ use apsis_math::Vec3;
|
|||
///
|
||||
/// Angles are in radians and the semi-major axis is in kilometers. The orbit
|
||||
/// is elliptical, so the eccentricity must be below one.
|
||||
///
|
||||
/// 轨道的六个开普勒根数。
|
||||
///
|
||||
/// 形状与大小由半长轴与偏心率给出,定向由倾角、升交点赤经与近地点幅角给出,
|
||||
/// 轨道上的位置由真近点角给出。
|
||||
///
|
||||
/// 角度单位为弧度,半长轴单位为千米。轨道为椭圆,故偏心率必须小于一。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct KeplerianElements {
|
||||
/// Semi-major axis, in kilometers. Must be positive.
|
||||
///
|
||||
/// 半长轴,单位为千米,必须为正。
|
||||
pub semi_major_axis: f64,
|
||||
/// Eccentricity, dimensionless. Must be in `[0, 1)`.
|
||||
///
|
||||
/// 偏心率,无量纲,必须位于 `[0, 1)`。
|
||||
pub eccentricity: f64,
|
||||
/// Inclination, in radians. Must be in `[0, pi]`.
|
||||
///
|
||||
/// 倾角,单位为弧度,必须位于 `[0, pi]`。
|
||||
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,
|
||||
/// True anomaly, in radians.
|
||||
///
|
||||
/// 真近点角,单位为弧度。
|
||||
pub true_anomaly: f64,
|
||||
}
|
||||
|
||||
|
|
@ -35,16 +54,27 @@ pub struct KeplerianElements {
|
|||
/// Position is in kilometers and velocity is in kilometers per second. The
|
||||
/// frame has its first axis toward the reference direction, its third axis
|
||||
/// along the orbit normal, and its second completing a right-handed set.
|
||||
///
|
||||
/// 卫星在根数所参照惯性系中的位置与速度。
|
||||
///
|
||||
/// 位置单位为千米,速度单位为千米每秒。该系第一轴指向参考方向,第三轴沿轨道法线,
|
||||
/// 第二轴构成右手系。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct State {
|
||||
/// Position, in kilometers.
|
||||
///
|
||||
/// 位置,单位为千米。
|
||||
pub position: Vec3,
|
||||
/// Velocity, in kilometers per second.
|
||||
///
|
||||
/// 速度,单位为千米每秒。
|
||||
pub velocity: Vec3,
|
||||
}
|
||||
|
||||
impl KeplerianElements {
|
||||
/// Returns `true` when the elements describe an elliptical orbit.
|
||||
///
|
||||
/// 当这些根数描述一条椭圆轨道时返回 `true`。
|
||||
pub fn is_valid(self) -> bool {
|
||||
self.semi_major_axis.is_finite()
|
||||
&& self.semi_major_axis > 0.0
|
||||
|
|
@ -61,11 +91,17 @@ impl KeplerianElements {
|
|||
///
|
||||
/// This is `a (1 - e^2)`, the distance from the focus to the orbit
|
||||
/// measured perpendicular to the major axis.
|
||||
///
|
||||
/// 返回半通径,单位为千米。
|
||||
///
|
||||
/// 它等于 `a (1 - e^2)`,即从焦点沿垂直于长轴方向量到轨道的距离。
|
||||
pub fn semi_latus_rectum(self) -> f64 {
|
||||
self.semi_major_axis * (1.0 - self.eccentricity * self.eccentricity)
|
||||
}
|
||||
|
||||
/// Returns the semi-minor axis, in kilometers.
|
||||
///
|
||||
/// 返回半短轴,单位为千米。
|
||||
pub fn semi_minor_axis(self) -> f64 {
|
||||
self.semi_major_axis * (1.0 - self.eccentricity * self.eccentricity).sqrt()
|
||||
}
|
||||
|
|
@ -73,6 +109,10 @@ impl KeplerianElements {
|
|||
/// Returns the perigee radius, in kilometers.
|
||||
///
|
||||
/// This is the closest approach to the focus, `a (1 - e)`.
|
||||
///
|
||||
/// 返回近地点半径,单位为千米。
|
||||
///
|
||||
/// 它是到焦点的最近距离 `a (1 - e)`。
|
||||
pub fn perigee_radius(self) -> f64 {
|
||||
self.semi_major_axis * (1.0 - self.eccentricity)
|
||||
}
|
||||
|
|
@ -80,6 +120,10 @@ impl KeplerianElements {
|
|||
/// Returns the apogee radius, in kilometers.
|
||||
///
|
||||
/// This is the farthest point from the focus, `a (1 + e)`.
|
||||
///
|
||||
/// 返回远地点半径,单位为千米。
|
||||
///
|
||||
/// 它是离焦点最远的点 `a (1 + e)`。
|
||||
pub fn apogee_radius(self) -> f64 {
|
||||
self.semi_major_axis * (1.0 + self.eccentricity)
|
||||
}
|
||||
|
|
@ -87,16 +131,24 @@ impl KeplerianElements {
|
|||
/// Returns the orbital period, in seconds.
|
||||
///
|
||||
/// This is Kepler's third law, `T = 2 pi sqrt(a^3 / mu)`.
|
||||
///
|
||||
/// 返回轨道周期,单位为秒。
|
||||
///
|
||||
/// 这是开普勒第三定律,`T = 2 pi sqrt(a^3 / mu)`。
|
||||
pub fn period(self, parameter: GravityParameter) -> f64 {
|
||||
2.0 * core::f64::consts::PI * (self.semi_major_axis.powi(3) / parameter.value()).sqrt()
|
||||
}
|
||||
|
||||
/// Returns the eccentric anomaly corresponding to the true anomaly.
|
||||
///
|
||||
/// 返回与真近点角对应的偏近点角。
|
||||
pub fn eccentric_anomaly(self) -> f64 {
|
||||
crate::anomaly::eccentric_from_true(self.true_anomaly, self.eccentricity)
|
||||
}
|
||||
|
||||
/// Returns the mean anomaly corresponding to the true anomaly.
|
||||
///
|
||||
/// 返回与真近点角对应的平近点角。
|
||||
pub fn mean_anomaly(self) -> f64 {
|
||||
crate::anomaly::mean_from_true(self.true_anomaly, self.eccentricity)
|
||||
}
|
||||
|
|
@ -117,6 +169,21 @@ impl KeplerianElements {
|
|||
///
|
||||
/// and then rotates by the argument of perigee, the inclination, and the
|
||||
/// right ascension of the ascending node.
|
||||
///
|
||||
/// 把这些根数转换为位置与速度。
|
||||
///
|
||||
/// 当根数不是有效椭圆轨道时返回 `None`。
|
||||
///
|
||||
/// 换算先在近点坐标系中定位卫星,该系第一轴指向近地点、第三轴沿轨道法线,
|
||||
///
|
||||
/// ```text
|
||||
/// r = p / (1 + e cos nu)
|
||||
/// x = r cos nu, y = r sin nu, z = 0
|
||||
/// vx = -sqrt(mu / p) sin nu
|
||||
/// vy = sqrt(mu / p) (e + cos nu)
|
||||
/// ```
|
||||
///
|
||||
/// 再依次按近地点幅角、倾角与升交点赤经旋转。
|
||||
pub fn to_state(self, parameter: GravityParameter) -> Option<State> {
|
||||
if !self.is_valid() {
|
||||
return None;
|
||||
|
|
@ -135,6 +202,8 @@ impl KeplerianElements {
|
|||
// Rotate from the perifocal frame to the inertial frame by the
|
||||
// argument of perigee about the third axis, the inclination about
|
||||
// the first, and the right ascension about the third.
|
||||
// 从近点坐标系旋转到惯性系:先绕第三轴转近地点幅角,再绕第一轴转倾角,
|
||||
// 最后绕第三轴转赤经。
|
||||
let rotation = perifocal_to_inertial(
|
||||
self.argument_of_perigee,
|
||||
self.inclination,
|
||||
|
|
@ -152,6 +221,11 @@ impl KeplerianElements {
|
|||
/// or when the orbit is not elliptical. This conversion assumes the
|
||||
/// orbit is bound, so a parabolic or hyperbolic state is rejected rather
|
||||
/// than returned with an eccentricity at or above one.
|
||||
///
|
||||
/// 由位置与速度返回对应的根数。
|
||||
///
|
||||
/// 当状态非有限、半径为零或轨道非椭圆时返回 `None`。该换算假定轨道受束缚,
|
||||
/// 因此抛物线或双曲线状态被拒绝,而不是返回偏心率大于等于一的结果。
|
||||
pub fn from_state(state: &State, parameter: GravityParameter) -> Option<Self> {
|
||||
let position = state.position;
|
||||
let velocity = state.velocity;
|
||||
|
|
@ -169,15 +243,18 @@ impl KeplerianElements {
|
|||
|
||||
// The specific angular momentum and the node vector, which together
|
||||
// give the orientation.
|
||||
// 比角动量与交点向量,二者共同给出轨道定向。
|
||||
let angular_momentum = position.cross(velocity);
|
||||
let angular_momentum_norm = angular_momentum.norm();
|
||||
if angular_momentum_norm == 0.0 {
|
||||
// A purely radial trajectory has no defined orbit plane.
|
||||
// 纯径向轨迹没有良定义的轨道平面。
|
||||
return None;
|
||||
}
|
||||
|
||||
// The eccentricity vector points toward perigee and has magnitude
|
||||
// equal to the eccentricity.
|
||||
// 偏心率矢量指向近地点,其模等于偏心率。
|
||||
let eccentricity_vector =
|
||||
position * (velocity_squared - mu / radius) - velocity * (radius * radial_velocity);
|
||||
let eccentricity = eccentricity_vector.norm() / mu;
|
||||
|
|
@ -186,6 +263,7 @@ impl KeplerianElements {
|
|||
}
|
||||
|
||||
// The specific mechanical energy gives the semi-major axis.
|
||||
// 单位质量机械能给出半长轴。
|
||||
let energy = velocity_squared / 2.0 - mu / radius;
|
||||
if energy == 0.0 {
|
||||
return None;
|
||||
|
|
@ -205,6 +283,8 @@ impl KeplerianElements {
|
|||
// inclination and for a circular orbit respectively; in those cases
|
||||
// the standard convention sets them to zero and measures the angle
|
||||
// from the reference direction instead.
|
||||
// 倾角为零时交点未定义,圆轨道时近地点幅角未定义;在这两种情形下,标准
|
||||
// 约定将其置零,并改为自参考方向量取该角。
|
||||
let right_ascension = if node_norm == 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
|
|
@ -215,6 +295,7 @@ impl KeplerianElements {
|
|||
0.0
|
||||
} else if node_norm == 0.0 {
|
||||
// Equatorial orbit: measure the argument from the first axis.
|
||||
// 赤道轨道:自第一轴量取该幅角。
|
||||
wrap_to_pi(eccentricity_vector.y.atan2(eccentricity_vector.x))
|
||||
} else {
|
||||
let value = (node.dot(eccentricity_vector) / (node_norm * eccentricity_vector.norm()))
|
||||
|
|
@ -230,6 +311,8 @@ impl KeplerianElements {
|
|||
// The true anomaly follows from the eccentric anomaly recovered
|
||||
// from the radial distance and the radial velocity, which is more
|
||||
// accurate near a circular orbit than the angle between vectors.
|
||||
// 真近点角由径向距离与径向速度求得的偏近点角推出;在近圆轨道处,这比
|
||||
// 直接求两矢量夹角更准确。
|
||||
let true_anomaly = true_anomaly_from_state(position, velocity, eccentricity, mu);
|
||||
|
||||
let elements = Self {
|
||||
|
|
@ -244,6 +327,8 @@ impl KeplerianElements {
|
|||
}
|
||||
|
||||
/// Returns a copy of these elements with a different true anomaly.
|
||||
///
|
||||
/// 返回真近点角不同的这些根数的副本。
|
||||
pub fn with_true_anomaly(self, true_anomaly: f64) -> Self {
|
||||
Self {
|
||||
true_anomaly,
|
||||
|
|
@ -255,6 +340,10 @@ impl KeplerianElements {
|
|||
///
|
||||
/// The true anomaly is recovered from the mean anomaly through Kepler's
|
||||
/// equation.
|
||||
///
|
||||
/// 返回给定平近点角处这些根数的副本。
|
||||
///
|
||||
/// 真近点角由平近点角经开普勒方程求得。
|
||||
pub fn with_mean_anomaly(self, mean_anomaly: f64) -> Self {
|
||||
let eccentric = eccentric_from_mean(mean_anomaly, self.eccentricity);
|
||||
Self {
|
||||
|
|
@ -268,6 +357,11 @@ impl KeplerianElements {
|
|||
///
|
||||
/// This is the same value as [`KeplerianElements::mean_anomaly`], named
|
||||
/// for symmetry with [`KeplerianElements::with_mean_anomaly`].
|
||||
///
|
||||
/// 返回平近点角,即沿轨道的时间线性度量。
|
||||
///
|
||||
/// 其值与 [`KeplerianElements::mean_anomaly`] 相同,此处命名是为了与
|
||||
/// [`KeplerianElements::with_mean_anomaly`] 对称。
|
||||
pub fn to_mean_anomaly(self) -> f64 {
|
||||
mean_from_eccentric(self.eccentric_anomaly(), self.eccentricity)
|
||||
}
|
||||
|
|
@ -278,6 +372,11 @@ impl KeplerianElements {
|
|||
/// The rotation is a product of three elementary rotations: by the argument
|
||||
/// of perigee about the third axis, then the inclination about the first,
|
||||
/// then the right ascension about the third.
|
||||
///
|
||||
/// 返回从近点坐标系到惯性系的旋转。
|
||||
///
|
||||
/// 该旋转是三个基本旋转之积:先绕第三轴转近地点幅角,再绕第一轴转倾角,最后
|
||||
/// 绕第三轴转赤经。
|
||||
pub fn perifocal_to_inertial(
|
||||
argument_of_perigee: f64,
|
||||
inclination: f64,
|
||||
|
|
@ -290,6 +389,8 @@ pub fn perifocal_to_inertial(
|
|||
|
||||
// Expanded product R3(O) R1(i) R3(w). Writing it out avoids three matrix
|
||||
// products and keeps the result well conditioned.
|
||||
// 展开后的乘积 R3(O) R1(i) R3(w)。直接写出可省去三次矩阵相乘,并使结果
|
||||
// 保持良态。
|
||||
Mat3::from_rows(
|
||||
[
|
||||
cos_o * cos_w - sin_o * sin_w * cos_i,
|
||||
|
|
@ -322,6 +423,20 @@ pub fn perifocal_to_inertial(
|
|||
/// handled by measuring the angle from the node instead, which is the same
|
||||
/// convention [`KeplerianElements::from_state`] uses for the argument of
|
||||
/// perigee.
|
||||
///
|
||||
/// 由状态矢量反推真近点角。
|
||||
///
|
||||
/// 以偏近点角为中间量,可在近圆轨道处保持精度,因为此时偏心率矢量很短、方向
|
||||
/// 难以确定。偏近点角由径向距离与径向速度经下式求得:
|
||||
///
|
||||
/// ```text
|
||||
/// cos E = (1 - r / a) / e
|
||||
/// sin E = r rdot / (e sqrt(mu a))
|
||||
/// ```
|
||||
///
|
||||
/// 真近点角再由偏近点角推出。对偏心率过小而使上述式子失效的圆轨道,改为
|
||||
/// 自交点量取夹角处理,这与 [`KeplerianElements::from_state`] 处理近地点幅角
|
||||
/// 时所用的约定相同。
|
||||
fn true_anomaly_from_state(position: Vec3, velocity: Vec3, eccentricity: f64, mu: f64) -> f64 {
|
||||
let radius = position.norm();
|
||||
let radial_velocity = position.dot(velocity) / radius;
|
||||
|
|
@ -333,16 +448,20 @@ fn true_anomaly_from_state(position: Vec3, velocity: Vec3, eccentricity: f64, mu
|
|||
// Below this eccentricity the angle is recovered from the node instead,
|
||||
// because the division by the eccentricity would amplify the rounding
|
||||
// in the radial terms.
|
||||
// 偏心率低于此值时改为自交点反求夹角,因为除以偏心率会放大径向项中的
|
||||
// 舍入误差。
|
||||
const CIRCULAR_THRESHOLD: f64 = 1e-8;
|
||||
if eccentricity < CIRCULAR_THRESHOLD {
|
||||
let angular_momentum = position.cross(velocity);
|
||||
let node = Vec3::Z.cross(angular_momentum);
|
||||
if node.norm() == 0.0 {
|
||||
// Equatorial circular orbit: measure from the first axis.
|
||||
// 赤道圆轨道:自第一轴量取。
|
||||
return wrap_to_pi(position.y.atan2(position.x));
|
||||
}
|
||||
// The argument of latitude is the angle from the node to the
|
||||
// position, measured in the orbit plane.
|
||||
// 纬度幅角是自交点到该位置在轨道平面内量取的夹角。
|
||||
let node_unit = node.normalize().expect("node is non-zero");
|
||||
let in_plane = position.normalize().expect("radius is non-zero");
|
||||
let cross = node_unit.cross(in_plane);
|
||||
|
|
|
|||
|
|
@ -7,23 +7,36 @@
|
|||
/// models, and the correct choice is the one that matches the source of the
|
||||
/// orbital elements. For elements derived from a two-line element set, the
|
||||
/// WGS 72 value is the one the SGP4 theory was fitted to.
|
||||
///
|
||||
/// 中心天体的引力参数,单位为立方千米每二次方秒。
|
||||
///
|
||||
/// 提供两个值是因为工作区支持两种重力模型,正确选择取决于轨道根数的来源。对
|
||||
/// 由两线根数集导出的根数,应取 WGS 72 值,即 SGP4 理论拟合所用的那一个。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct GravityParameter(pub f64);
|
||||
|
||||
impl GravityParameter {
|
||||
/// The WGS 72 gravitational parameter, `398 600.8 km^3/s^2`.
|
||||
///
|
||||
/// WGS 72 引力参数,`398 600.8 km^3/s^2`。
|
||||
pub const WGS72: Self = Self(398_600.8);
|
||||
|
||||
/// The WGS 84 gravitational parameter, `398 600.5 km^3/s^2`.
|
||||
///
|
||||
/// WGS 84 引力参数,`398 600.5 km^3/s^2`。
|
||||
pub const WGS84: Self = Self(398_600.5);
|
||||
|
||||
/// Returns the value in cubic kilometers per second squared.
|
||||
///
|
||||
/// 返回该值,单位为立方千米每二次方秒。
|
||||
pub const fn value(self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the square root of the parameter, in kilometers to the three
|
||||
/// halves per second.
|
||||
///
|
||||
/// 返回该参数的平方根,单位为千米的三分之二次方每秒。
|
||||
pub fn sqrt(self) -> f64 {
|
||||
self.0.sqrt()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,24 @@ use apsis_math::Vec3;
|
|||
/// fires along a body axis that rotates as the spacecraft turns to hold a
|
||||
/// direction, and modelling that requires the attitude history, which is not
|
||||
/// part of this crate.
|
||||
///
|
||||
/// 在一段时间内施加恒定加速度的机动。
|
||||
///
|
||||
/// 有限推力点火适用于推力器工作时间足够长、其时长无法忽略的情形:电推力器的
|
||||
/// 点火可能持续轨道相当大一部分,任何推力期间航天器有明显位移的点火亦属此类。
|
||||
///
|
||||
/// 加速度在调用方选定的惯性系中保持恒定。这是本模型所作的简化:真实推力器
|
||||
/// 沿随航天器转动以保持方向的本体轴喷焰,建模这一点需要姿态历史,而它不属
|
||||
/// 于本 crate 的范围。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct FiniteBurn {
|
||||
/// The acceleration vector, in kilometers per second squared.
|
||||
///
|
||||
/// 加速度矢量,单位为千米每二次方秒。
|
||||
acceleration: Vec3,
|
||||
/// The duration, in seconds.
|
||||
///
|
||||
/// 时长,单位为秒。
|
||||
duration: f64,
|
||||
}
|
||||
|
||||
|
|
@ -29,6 +42,10 @@ impl FiniteBurn {
|
|||
///
|
||||
/// Returns an error when a value is not finite, when the duration is not
|
||||
/// positive, or when the acceleration is zero.
|
||||
///
|
||||
/// 由加速度与时长创建一次点火。
|
||||
///
|
||||
/// 当某个值非有限、时长不为正或加速度为零时返回错误。
|
||||
pub fn new(acceleration: Vec3, duration: f64) -> Result<Self, ManeuverError> {
|
||||
if !acceleration.is_finite() || !duration.is_finite() {
|
||||
return Err(ManeuverError::new(ManeuverErrorKind::NotFinite));
|
||||
|
|
@ -53,6 +70,10 @@ impl FiniteBurn {
|
|||
///
|
||||
/// The direction is normalized, so the acceleration magnitude is fixed
|
||||
/// whatever the length of the direction vector.
|
||||
///
|
||||
/// 沿给定方向创建指定大小加速度的点火。
|
||||
///
|
||||
/// 方向被归一化,故无论方向矢量多长,加速度大小都固定不变。
|
||||
pub fn along(direction: Vec3, acceleration: f64, duration: f64) -> Result<Self, ManeuverError> {
|
||||
if !acceleration.is_finite() || acceleration <= 0.0 {
|
||||
return Err(
|
||||
|
|
@ -68,17 +89,23 @@ impl FiniteBurn {
|
|||
}
|
||||
|
||||
/// Returns the acceleration vector, in kilometers per second squared.
|
||||
///
|
||||
/// 返回加速度矢量,单位为千米每二次方秒。
|
||||
pub const fn acceleration(self) -> Vec3 {
|
||||
self.acceleration
|
||||
}
|
||||
|
||||
/// Returns the magnitude of the acceleration, in kilometers per second
|
||||
/// squared.
|
||||
///
|
||||
/// 返回加速度大小,单位为千米每二次方秒。
|
||||
pub fn acceleration_magnitude(self) -> f64 {
|
||||
self.acceleration.norm()
|
||||
}
|
||||
|
||||
/// Returns the duration, in seconds.
|
||||
///
|
||||
/// 返回时长,单位为秒。
|
||||
pub const fn duration(self) -> f64 {
|
||||
self.duration
|
||||
}
|
||||
|
|
@ -91,6 +118,12 @@ impl FiniteBurn {
|
|||
/// achieved change is close to this for a short burn and differs from it
|
||||
/// for a long one, which is the difference the finite model exists to
|
||||
/// capture.
|
||||
///
|
||||
/// 返回该加速度在自由空间中会造成的速度变化,单位为千米每秒。
|
||||
///
|
||||
/// 它是加速度乘以时长,即点火在不受引力作用时会提供的 delta-v。在引力
|
||||
/// 作用下,短点火的实际变化与之接近,长点火则不同,这正是有限推力模型
|
||||
/// 得以存在的意义。
|
||||
pub fn nominal_delta_v(self) -> f64 {
|
||||
self.acceleration.norm() * self.duration
|
||||
}
|
||||
|
|
@ -100,6 +133,11 @@ impl FiniteBurn {
|
|||
/// This is the impulsive burn the finite one approximates as its
|
||||
/// duration shrinks, and it is what [`FiniteBurn::apply`] returns in the
|
||||
/// limit of a constant gravity field.
|
||||
///
|
||||
/// 返回与标称变化等效的冲量。
|
||||
///
|
||||
/// 它是有限点火在时长趋零时所逼近的脉冲点火,也是 [`FiniteBurn::apply`]
|
||||
/// 在恒定引力场极限下给出的量。
|
||||
pub fn equivalent_impulse(self) -> Result<crate::impulse::Impulse, ManeuverError> {
|
||||
crate::impulse::Impulse::new(self.acceleration * self.duration)
|
||||
}
|
||||
|
|
@ -124,6 +162,20 @@ impl FiniteBurn {
|
|||
///
|
||||
/// Returns the state at the end of the burn together with the achieved
|
||||
/// velocity change.
|
||||
///
|
||||
/// 在二体引力下把该点火作用于一个状态。
|
||||
///
|
||||
/// 位置与速度在时长内积分前进,积分时把恒定加速度叠加到引力上。恒定加速度
|
||||
/// 加质点引力的精确解没有闭式,故用定步长四阶 Runge-Kutta 积分,对点火所占
|
||||
/// 的短弧段是精确的。
|
||||
///
|
||||
/// 实际达到的速度变化以同时长的滑行作为比较基准,而非以初始速度。区分这
|
||||
/// 一点很重要:即使无推力,引力在点火期间也会改变速度,故末速度与初速度
|
||||
/// 之差含速度矢量的自然转动。与滑行比较才能隔离出推力的贡献,即该次点火
|
||||
/// 实际买到的东西;这也使结果在时长趋零时收敛到标称变化,而与初速度
|
||||
/// 作差则不然。
|
||||
///
|
||||
/// 返回点火结束时的状态及实际达到的速度变化。
|
||||
pub fn apply(
|
||||
self,
|
||||
state: State,
|
||||
|
|
@ -141,6 +193,7 @@ impl FiniteBurn {
|
|||
let step = self.duration / steps as f64;
|
||||
|
||||
// The two-body acceleration at a position.
|
||||
// 某位置处的二体加速度。
|
||||
let gravity = |position: Vec3| -> Vec3 {
|
||||
let radius = position.norm();
|
||||
if radius == 0.0 {
|
||||
|
|
@ -149,9 +202,11 @@ impl FiniteBurn {
|
|||
position * (-mu / (radius * radius * radius))
|
||||
};
|
||||
// The thrust added to gravity, which is what the burn integrates.
|
||||
// 叠加到引力上的推力,即点火所积分的量。
|
||||
let thrust_and_gravity = |position: Vec3| -> Vec3 { gravity(position) + self.acceleration };
|
||||
|
||||
// The burn trajectory.
|
||||
// 点火轨迹。
|
||||
let mut position = state.position;
|
||||
let mut velocity = state.velocity;
|
||||
for _ in 0..steps {
|
||||
|
|
@ -163,6 +218,7 @@ impl FiniteBurn {
|
|||
|
||||
// The coast trajectory over the same duration, with the same
|
||||
// integrator and step, so the only difference is the thrust.
|
||||
// 同时长、同积分器同步长的滑行轨迹,故二者唯一差别是推力。
|
||||
let mut coast_position = state.position;
|
||||
let mut coast_velocity = state.velocity;
|
||||
for _ in 0..steps {
|
||||
|
|
@ -192,6 +248,11 @@ impl FiniteBurn {
|
|||
/// not valid, when the integration fails, or when the resulting state is
|
||||
/// no longer an elliptical orbit, which happens when the burn is large
|
||||
/// enough to escape.
|
||||
///
|
||||
/// 把该点火作用于一组开普勒根数。
|
||||
///
|
||||
/// 先把根数转为状态,对状态施加点火,再转回根数。当根数无效、积分失败,或
|
||||
/// 结果状态不再是椭圆轨道时返回错误;后者在点火足够大以致逃逸时发生。
|
||||
pub fn apply_to_elements(
|
||||
self,
|
||||
elements: KeplerianElements,
|
||||
|
|
@ -211,21 +272,36 @@ impl FiniteBurn {
|
|||
/// The outcome keeps the final state, the change the burn achieved, and the
|
||||
/// change the acceleration would have given in free space, so a caller can
|
||||
/// see how much gravity cost or added during the burn.
|
||||
///
|
||||
/// 一次有限推力点火的产物。
|
||||
///
|
||||
/// 结果保留末状态、点火实际达到的变化,以及该加速度在自由空间中会造成的
|
||||
/// 变化,使调用方可以看出点火期间引力造成或增加了多少。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct BurnOutcome {
|
||||
/// The state at the end of the burn.
|
||||
///
|
||||
/// 点火结束时的状态。
|
||||
pub state: State,
|
||||
/// The velocity change actually achieved, in kilometers per second.
|
||||
///
|
||||
/// 实际达到的速度变化,单位为千米每秒。
|
||||
pub achieved_delta_v: Vec3,
|
||||
/// The velocity change the acceleration would give in free space, in
|
||||
/// kilometers per second.
|
||||
///
|
||||
/// 该加速度在自由空间中会造成的速度变化,单位为千米每秒。
|
||||
pub nominal_delta_v: f64,
|
||||
/// The burn duration, in seconds.
|
||||
///
|
||||
/// 点火时长,单位为秒。
|
||||
pub duration: f64,
|
||||
}
|
||||
|
||||
impl BurnOutcome {
|
||||
/// Returns the magnitude of the achieved velocity change.
|
||||
///
|
||||
/// 返回实际达到的速度变化的大小。
|
||||
pub fn achieved_magnitude(&self) -> f64 {
|
||||
self.achieved_delta_v.norm()
|
||||
}
|
||||
|
|
@ -235,6 +311,11 @@ impl BurnOutcome {
|
|||
///
|
||||
/// A negative value means gravity turned the velocity against the thrust
|
||||
/// during the burn, so the burn bought less than its free space delta-v.
|
||||
///
|
||||
/// 返回实际变化与标称变化之差,单位为千米每秒。
|
||||
///
|
||||
/// 负值表示点火期间引力把速度转向了推力的反方向,使该次点火的收获小于其
|
||||
/// 自由空间 delta-v。
|
||||
pub fn gravity_loss(&self) -> f64 {
|
||||
self.achieved_magnitude() - self.nominal_delta_v
|
||||
}
|
||||
|
|
@ -243,6 +324,10 @@ impl BurnOutcome {
|
|||
///
|
||||
/// Returns `None` when the final state is not an elliptical orbit, which
|
||||
/// happens when the burn is large enough to escape.
|
||||
///
|
||||
/// 返回点火结束时的根数。
|
||||
///
|
||||
/// 当末状态不是椭圆轨道时返回 `None`;这在点火足够大以致逃逸时发生。
|
||||
pub fn elements(&self, parameter: GravityParameter) -> Option<KeplerianElements> {
|
||||
KeplerianElements::from_state(&self.state, parameter)
|
||||
}
|
||||
|
|
@ -255,6 +340,11 @@ impl BurnOutcome {
|
|||
/// `dr/dt = v` and `dv/dt = a(r)`, with `a` combining thrust and gravity.
|
||||
/// The four stages are the standard ones; writing them out keeps the loop
|
||||
/// allocation free.
|
||||
///
|
||||
/// 二体方程加上恒定加速度的一步经典四阶 Runge-Kutta。
|
||||
///
|
||||
/// 状态为 `(位置, 速度)`,方程即通常的 `dr/dt = v` 与 `dv/dt = a(r)`,其中
|
||||
/// `a` 合并推力与引力。四个级次是标准形式;直接写出使循环无需分配内存。
|
||||
fn rk4_step(
|
||||
position: Vec3,
|
||||
velocity: Vec3,
|
||||
|
|
|
|||
|
|
@ -3,20 +3,34 @@
|
|||
use core::fmt;
|
||||
|
||||
/// The category of a maneuver error.
|
||||
///
|
||||
/// 机动错误的类别。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum ManeuverErrorKind {
|
||||
/// A velocity change, acceleration, or duration is not finite.
|
||||
///
|
||||
/// 速度变化、加速度或时长非有限。
|
||||
NotFinite,
|
||||
/// The duration of a burn is not positive.
|
||||
///
|
||||
/// 点火时长不为正。
|
||||
NonPositiveDuration,
|
||||
/// The acceleration of a burn is not positive.
|
||||
///
|
||||
/// 点火加速度不为正。
|
||||
NonPositiveAcceleration,
|
||||
/// A radius is not finite or not positive.
|
||||
///
|
||||
/// 某半径非有限或不为正。
|
||||
InvalidRadius,
|
||||
/// The two radii of a transfer are equal, so there is nothing to do.
|
||||
///
|
||||
/// 转移的两个半径相等,因而无事可做。
|
||||
DegenerateTransfer,
|
||||
/// A state is missing the data the operation needs.
|
||||
///
|
||||
/// 某状态缺少该运算所需的数据。
|
||||
InvalidState,
|
||||
}
|
||||
|
||||
|
|
@ -35,6 +49,8 @@ impl fmt::Display for ManeuverErrorKind {
|
|||
}
|
||||
|
||||
/// A failure while building or applying a maneuver.
|
||||
///
|
||||
/// 构建或应用机动时发生的失败。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ManeuverError {
|
||||
kind: ManeuverErrorKind,
|
||||
|
|
@ -43,22 +59,30 @@ pub struct ManeuverError {
|
|||
|
||||
impl ManeuverError {
|
||||
/// Creates an error with the given category.
|
||||
///
|
||||
/// 用给定类别创建一个错误。
|
||||
pub fn new(kind: ManeuverErrorKind) -> Self {
|
||||
Self { kind, detail: None }
|
||||
}
|
||||
|
||||
/// Attaches a description of the value that caused the failure.
|
||||
///
|
||||
/// 附加对该失败所涉数值的说明。
|
||||
pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
|
||||
self.detail = Some(detail.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the category of this failure.
|
||||
///
|
||||
/// 返回该失败的类别。
|
||||
pub const fn kind(&self) -> ManeuverErrorKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
/// Returns the detail text attached to this error, if any.
|
||||
///
|
||||
/// 返回附加在该错误上的说明文本,若有。
|
||||
pub fn detail(&self) -> Option<&str> {
|
||||
self.detail.as_deref()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,21 +16,43 @@ use apsis_kepler::GravityParameter;
|
|||
/// raises the orbit and a negative one lowers it, so the same type covers a
|
||||
/// climb and a descent. The total is the sum of the magnitudes, which is the
|
||||
/// quantity a propellant budget uses.
|
||||
///
|
||||
/// 两个共面圆轨道之间的双脉冲转移。
|
||||
///
|
||||
/// 该转移即经典的 Hohmann 机动:一次点火进入椭圆,其近地点相切于较低轨道、
|
||||
/// 远地点相切于较高轨道,沿该椭圆滑行半个周期,再一次点火进入目标圆轨道。它
|
||||
/// 是脉冲模型中两个共面圆轨道之间最省的双脉冲转移,因此成为衡量其他转移的
|
||||
/// 基准。
|
||||
///
|
||||
/// 两次点火沿运动方向带符号:正值抬升轨道,负值降低轨道,故同一类型同时
|
||||
/// 表达上升与下降。总和取两者模之和,即推进剂预算所用的量。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct HohmannTransfer {
|
||||
/// The radius of the orbit the transfer starts from, in kilometers.
|
||||
///
|
||||
/// 转移起始轨道的半径,单位为千米。
|
||||
pub from_radius: f64,
|
||||
/// The radius of the orbit the transfer arrives at, in kilometers.
|
||||
///
|
||||
/// 转移到达轨道的半径,单位为千米。
|
||||
pub to_radius: f64,
|
||||
/// The semi-major axis of the transfer ellipse, in kilometers.
|
||||
///
|
||||
/// 转移椭圆的半长轴,单位为千米。
|
||||
pub transfer_semi_major_axis: f64,
|
||||
/// The first burn, along the direction of motion, in kilometers per
|
||||
/// second.
|
||||
///
|
||||
/// 第一次点火,沿运动方向,单位为千米每秒。
|
||||
pub first_burn: f64,
|
||||
/// The second burn, along the direction of motion, in kilometers per
|
||||
/// second.
|
||||
///
|
||||
/// 第二次点火,沿运动方向,单位为千米每秒。
|
||||
pub second_burn: f64,
|
||||
/// The coast time between the burns, in seconds.
|
||||
///
|
||||
/// 两次点火之间的滑行时间,单位为秒。
|
||||
pub transfer_time: f64,
|
||||
}
|
||||
|
||||
|
|
@ -39,21 +61,31 @@ impl HohmannTransfer {
|
|||
///
|
||||
/// This is the sum of the magnitudes of the two burns, which is what the
|
||||
/// transfer costs whatever its direction.
|
||||
///
|
||||
/// 返回总速度变化,单位为千米每秒。
|
||||
///
|
||||
/// 它是两次点火大小之和,即无论方向如何该转移所需的代价。
|
||||
pub fn total_delta_v(&self) -> f64 {
|
||||
self.first_burn.abs() + self.second_burn.abs()
|
||||
}
|
||||
|
||||
/// Returns `true` when the transfer raises the orbit.
|
||||
///
|
||||
/// 当该转移抬升轨道时返回 `true`。
|
||||
pub fn is_raising(&self) -> bool {
|
||||
self.to_radius > self.from_radius
|
||||
}
|
||||
|
||||
/// Returns the velocity change of the first burn, unsigned.
|
||||
///
|
||||
/// 返回第一次点火的速度变化,不带符号。
|
||||
pub fn first_burn_magnitude(&self) -> f64 {
|
||||
self.first_burn.abs()
|
||||
}
|
||||
|
||||
/// Returns the velocity change of the second burn, unsigned.
|
||||
///
|
||||
/// 返回第二次点火的速度变化,不带符号。
|
||||
pub fn second_burn_magnitude(&self) -> f64 {
|
||||
self.second_burn.abs()
|
||||
}
|
||||
|
|
@ -63,6 +95,10 @@ impl HohmannTransfer {
|
|||
///
|
||||
/// This is the semi-major axis of the ellipse, which is the arithmetic
|
||||
/// mean of the two orbit radii.
|
||||
///
|
||||
/// 返回该转移所对称的平均半径,单位为千米。
|
||||
///
|
||||
/// 它即该椭圆的半长轴,为两个轨道半径的算术平均。
|
||||
pub fn mean_radius(&self) -> f64 {
|
||||
self.transfer_semi_major_axis
|
||||
}
|
||||
|
|
@ -86,6 +122,20 @@ impl HohmannTransfer {
|
|||
/// the ellipse there, taken the other way round because the ellipse arrives
|
||||
/// slower than the circle it must join. The coast time is half the period of
|
||||
/// the ellipse.
|
||||
///
|
||||
/// 返回两个圆轨道之间的双脉冲转移。
|
||||
///
|
||||
/// 半径单位为千米,须有限且为正。二者可按任意次序给出:第二个半径较大者为
|
||||
/// 上升,较小者为下降。
|
||||
///
|
||||
/// 当某半径不可用或二者相等时返回错误,因为轨道到其自身的转移不是机动,且
|
||||
/// 几乎总是计划中的笔误。
|
||||
///
|
||||
/// 两次点火由活力公式导出。在第一个半径处圆轨道速度为 `sqrt(mu / r1)`,而
|
||||
/// 转移椭圆上的速度为 `sqrt(mu (2 / r1 - 1 / a))`,其中 `a` 为椭圆半长轴,
|
||||
/// 第一次点火即二者之差。第二次点火是目标处圆轨道速度与椭圆上速度之差,取
|
||||
/// 相反方向,因为椭圆到达时比它必须汇入的圆轨道更慢。滑行时间为该椭圆
|
||||
/// 周期的一半。
|
||||
pub fn hohmann_transfer(
|
||||
from_radius: f64,
|
||||
to_radius: f64,
|
||||
|
|
@ -106,11 +156,13 @@ pub fn hohmann_transfer(
|
|||
let transfer_semi_major_axis = (from_radius + to_radius) / 2.0;
|
||||
|
||||
// The circular speeds at each radius.
|
||||
// 各半径处的圆轨道速度。
|
||||
let circular_from = (mu / from_radius).sqrt();
|
||||
let circular_to = (mu / to_radius).sqrt();
|
||||
|
||||
// The speeds on the transfer ellipse at each radius, from the vis-viva
|
||||
// equation `v^2 = mu (2 / r - 1 / a)`.
|
||||
// 各半径处转移椭圆上的速度,由活力公式 `v^2 = mu (2 / r - 1 / a)` 得出。
|
||||
let transfer_from = (mu * (2.0 / from_radius - 1.0 / transfer_semi_major_axis)).sqrt();
|
||||
let transfer_to = (mu * (2.0 / to_radius - 1.0 / transfer_semi_major_axis)).sqrt();
|
||||
|
||||
|
|
@ -118,10 +170,13 @@ pub fn hohmann_transfer(
|
|||
// from the ellipse speed up to the circular speed at the destination.
|
||||
// Both are along the direction of motion, and the sign follows from
|
||||
// which speed is larger.
|
||||
// 第一次点火把卫星加速到椭圆上;第二次把椭圆上的速度加速到目标处圆轨道
|
||||
// 速度。两者均沿运动方向,符号取决于哪个速度更大。
|
||||
let first_burn = transfer_from - circular_from;
|
||||
let second_burn = circular_to - transfer_to;
|
||||
|
||||
// Half the period of the transfer ellipse.
|
||||
// 转移椭圆周期的一半。
|
||||
let transfer_time = core::f64::consts::PI * (transfer_semi_major_axis.powi(3) / mu).sqrt();
|
||||
|
||||
Ok(HohmannTransfer {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,14 @@ use apsis_math::Vec3;
|
|||
/// be applied to, so the caller is responsible for expressing it in that
|
||||
/// frame. The magnitude is the delta-v, the quantity a propellant budget is
|
||||
/// measured in.
|
||||
///
|
||||
/// 瞬时速度变化。
|
||||
///
|
||||
/// 冲量是在零时间内施加的速度变化,故只改变状态的速度而不改变其位置。这是相对
|
||||
/// 轨道周期很短的点火的模型,也是编写 delta-v 预算所用的模型。
|
||||
///
|
||||
/// 该变化以矢量形式存储在与待施加状态相同的坐标系中,因此调用方负责把它表达
|
||||
/// 在该系中。其模即 delta-v,推进剂预算以此计量。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Impulse {
|
||||
delta_v: Vec3,
|
||||
|
|
@ -26,6 +34,11 @@ impl Impulse {
|
|||
/// Returns an error when a component is not finite or when the vector is
|
||||
/// zero, because a zero change is not a maneuver and is almost always a
|
||||
/// mistake in a plan.
|
||||
///
|
||||
/// 由速度变化矢量创建冲量,单位为千米每秒。
|
||||
///
|
||||
/// 当某个分量非有限或矢量为零时返回错误,因为零变化不是机动,且几乎总是
|
||||
/// 计划中的笔误。
|
||||
pub fn new(delta_v: Vec3) -> Result<Self, ManeuverError> {
|
||||
if !delta_v.is_finite() {
|
||||
return Err(ManeuverError::new(ManeuverErrorKind::NotFinite)
|
||||
|
|
@ -44,6 +57,11 @@ impl Impulse {
|
|||
/// the length of the direction vector. Returns an error when the
|
||||
/// magnitude is not positive and finite or when the direction is zero or
|
||||
/// not finite.
|
||||
///
|
||||
/// 沿给定方向创建指定大小的冲量。
|
||||
///
|
||||
/// 方向被归一化,故无论方向矢量多长,其大小即 delta-v。当大小非正或非有限,
|
||||
/// 或方向为零或非有限时返回错误。
|
||||
pub fn along(direction: Vec3, magnitude: f64) -> Result<Self, ManeuverError> {
|
||||
if !magnitude.is_finite() || magnitude <= 0.0 {
|
||||
return Err(
|
||||
|
|
@ -59,6 +77,8 @@ impl Impulse {
|
|||
}
|
||||
|
||||
/// Returns the velocity change vector, in kilometers per second.
|
||||
///
|
||||
/// 返回速度变化矢量,单位为千米每秒。
|
||||
pub const fn delta_v(self) -> Vec3 {
|
||||
self.delta_v
|
||||
}
|
||||
|
|
@ -66,6 +86,10 @@ impl Impulse {
|
|||
/// Returns the magnitude of the change, in kilometers per second.
|
||||
///
|
||||
/// This is the delta-v the burn costs.
|
||||
///
|
||||
/// 返回该变化的大小,单位为千米每秒。
|
||||
///
|
||||
/// 这就是该次点火所需的 delta-v。
|
||||
pub fn magnitude(self) -> f64 {
|
||||
self.delta_v.norm()
|
||||
}
|
||||
|
|
@ -75,11 +99,18 @@ impl Impulse {
|
|||
/// A factor of zero produces a zero impulse, which [`Impulse::new`]
|
||||
/// rejects, so this returns an error for it rather than a value that
|
||||
/// cannot be built.
|
||||
///
|
||||
/// 返回该变化按某因子缩放后的副本。
|
||||
///
|
||||
/// 因子为零会得到零冲量,而 [`Impulse::new`] 拒绝这种情况,故本函数对该
|
||||
/// 因子返回错误,而不是给出一个无法构造的值。
|
||||
pub fn scaled(self, factor: f64) -> Result<Self, ManeuverError> {
|
||||
Self::new(self.delta_v * factor)
|
||||
}
|
||||
|
||||
/// Returns a copy of the change reversed, which undoes the burn.
|
||||
///
|
||||
/// 返回取反后的变化副本,即撤销该次点火。
|
||||
pub fn reversed(self) -> Self {
|
||||
Self {
|
||||
delta_v: -self.delta_v,
|
||||
|
|
@ -87,6 +118,8 @@ impl Impulse {
|
|||
}
|
||||
|
||||
/// Applies the impulse to a velocity, returning the new velocity.
|
||||
///
|
||||
/// 把该冲量作用于一个速度,返回新的速度。
|
||||
pub fn apply_to_velocity(self, velocity: Vec3) -> Vec3 {
|
||||
velocity + self.delta_v
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@ use core::fmt;
|
|||
/// The type pairs a prepared SGP4 satellite with the gravity model it was
|
||||
/// initialised against, because the osculating element conversion needs the
|
||||
/// same gravitational parameter to be consistent with the propagation.
|
||||
///
|
||||
/// 以平均根数形式持有、随时可传播的卫星轨道。
|
||||
///
|
||||
/// 该类型把准备好的 SGP4 卫星与其初始化时所用的重力模型配对,因为密切根数
|
||||
/// 换算需要同一个引力参数才能与传播保持一致。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Orbit {
|
||||
satellite: Satellite,
|
||||
|
|
@ -26,6 +31,11 @@ impl Orbit {
|
|||
/// against, which for a catalogued satellite means
|
||||
/// [`GravityModel::WGS72`]. Returns `None` when the elements are not
|
||||
/// propagatable.
|
||||
///
|
||||
/// 由已解析的根数集与重力模型准备轨道。
|
||||
///
|
||||
/// 重力模型须与该根数集拟合时所用者一致;对编目卫星即 [`GravityModel::WGS72`]。
|
||||
/// 当根数不可传播时返回 `None`。
|
||||
pub fn from_element(element: &TwoLineElement, model: GravityModel) -> Option<Self> {
|
||||
Self::try_from_element(element, model).ok()
|
||||
}
|
||||
|
|
@ -33,6 +43,10 @@ impl Orbit {
|
|||
/// Prepares an orbit from a parsed element set and a gravity model.
|
||||
///
|
||||
/// Returns an error describing which element is not propagatable.
|
||||
///
|
||||
/// 由已解析的根数集与重力模型准备轨道。
|
||||
///
|
||||
/// 返回一个说明哪个根数不可传播的错误。
|
||||
pub fn try_from_element(
|
||||
element: &TwoLineElement,
|
||||
model: GravityModel,
|
||||
|
|
@ -45,6 +59,8 @@ impl Orbit {
|
|||
}
|
||||
|
||||
/// Prepares an orbit from mean elements already in model units.
|
||||
///
|
||||
/// 由已按模型单位表示的平均根数准备轨道。
|
||||
pub fn from_elements(
|
||||
elements: &Elements,
|
||||
model: GravityModel,
|
||||
|
|
@ -57,6 +73,8 @@ impl Orbit {
|
|||
}
|
||||
|
||||
/// Returns the underlying propagator.
|
||||
///
|
||||
/// 返回底层的传播器。
|
||||
pub fn satellite(&self) -> &Satellite {
|
||||
&self.satellite
|
||||
}
|
||||
|
|
@ -65,6 +83,10 @@ impl Orbit {
|
|||
///
|
||||
/// The value is the one used for the osculating element conversion, so
|
||||
/// that the conversion is consistent with the propagation.
|
||||
///
|
||||
/// 返回与该重力模型配对的引力参数。
|
||||
///
|
||||
/// 该值即密切根数换算所用者,从而使换算与传播保持一致。
|
||||
pub fn gravity_parameter(&self) -> GravityParameter {
|
||||
self.parameter
|
||||
}
|
||||
|
|
@ -74,11 +96,17 @@ impl Orbit {
|
|||
///
|
||||
/// The epoch is the value written in the element set, which is the
|
||||
/// origin the propagator measures time from.
|
||||
///
|
||||
/// 返回根数历元,为 UTC 标度上的修正儒略日(Modified Julian Date)。
|
||||
///
|
||||
/// 该历元即根数集中写下的值,也是传播器度量的时间原点。
|
||||
pub fn epoch_mjd(&self) -> f64 {
|
||||
self.satellite.epoch_mjd()
|
||||
}
|
||||
|
||||
/// Returns `true` when the propagator uses the deep space branch.
|
||||
///
|
||||
/// 当传播器使用深空分支时返回 `true`。
|
||||
pub fn is_deep_space(&self) -> bool {
|
||||
self.satellite.is_deep_space()
|
||||
}
|
||||
|
|
@ -87,11 +115,17 @@ impl Orbit {
|
|||
///
|
||||
/// Position is in kilometers and velocity in kilometers per second, in
|
||||
/// the True Equator Mean Equinox frame.
|
||||
///
|
||||
/// 返回距根数历元若干分钟处的状态。
|
||||
///
|
||||
/// 位置单位为千米,速度单位为千米每秒,位于真赤道平春分点系。
|
||||
pub fn propagate_minutes(&self, minutes: f64) -> Result<State, PropagationError> {
|
||||
self.satellite.propagate_minutes(minutes)
|
||||
}
|
||||
|
||||
/// Returns the state at a Modified Julian Date on the UTC scale.
|
||||
///
|
||||
/// 返回 UTC 标度上某修正儒略日(Modified Julian Date)处的状态。
|
||||
pub fn propagate_at(&self, utc_mjd: f64) -> Result<State, PropagationError> {
|
||||
self.propagate_minutes((utc_mjd - self.satellite.epoch_mjd()) * 1_440.0)
|
||||
}
|
||||
|
|
@ -107,10 +141,19 @@ impl Orbit {
|
|||
/// Returns an error when the state cannot be propagated, or when the
|
||||
/// instantaneous orbit is not elliptical, which can happen for a state
|
||||
/// the propagation has pushed onto an escape trajectory.
|
||||
///
|
||||
/// 返回距根数历元若干分钟处的密切开普勒根数。
|
||||
///
|
||||
/// 密切根数描述与该瞬时位置和速度相匹配的二体轨道。它不是根数集的平均根数,
|
||||
/// 且沿轨道变化,因为 SGP4 含有二体模型所没有的摄动。
|
||||
///
|
||||
/// 当状态无法传播,或瞬时轨道非椭圆时返回错误;后者可发生于传播把状态推上
|
||||
/// 逃逸轨迹的情形。
|
||||
pub fn osculating_elements(&self, minutes: f64) -> Result<KeplerianElements, PropagationError> {
|
||||
let state = self.propagate_minutes(minutes)?;
|
||||
// The two crates define their own state type, so the components are
|
||||
// transferred explicitly rather than relying on a shared type.
|
||||
// 两个 crate 各自定义状态类型,故显式转移各分量,而不依赖共享类型。
|
||||
let kepler_state = apsis_kepler::State {
|
||||
position: state.position,
|
||||
velocity: state.velocity,
|
||||
|
|
@ -125,12 +168,18 @@ impl Orbit {
|
|||
///
|
||||
/// This is a convenience for the common case of wanting only the
|
||||
/// distance from the center of the Earth.
|
||||
///
|
||||
/// 返回状态矢量的模。
|
||||
///
|
||||
/// 这为只需到地心距离的常见情形提供便利。
|
||||
pub fn radius_at(&self, minutes: f64) -> Result<f64, PropagationError> {
|
||||
self.propagate_minutes(minutes)
|
||||
.map(|state| state.position.norm())
|
||||
}
|
||||
|
||||
/// Returns the position at a number of minutes from the element epoch.
|
||||
///
|
||||
/// 返回距根数历元若干分钟处的位置。
|
||||
pub fn position_at(&self, minutes: f64) -> Result<Vec3, PropagationError> {
|
||||
self.propagate_minutes(minutes).map(|state| state.position)
|
||||
}
|
||||
|
|
@ -150,6 +199,18 @@ impl Orbit {
|
|||
/// Returns an error when the grid is not usable or when a sample cannot
|
||||
/// be propagated, which happens when the elements have decayed or the
|
||||
/// range leaves the region where the model is defined.
|
||||
///
|
||||
/// 把该轨道在若干分钟的区间上采样为星历。
|
||||
///
|
||||
/// 传播是生成轨迹中开销最大的部分,故一次建表、多次查询值得占用内存。采样
|
||||
/// 时刻以距根数历元的分钟计,与本类型其余部分一致;返回的星历则用秒,与
|
||||
/// `apsis-ephemeris` 一致。
|
||||
///
|
||||
/// 该表用传播器给出的位置与速度建立,故 Hermite 插值具有精确的端点斜率,
|
||||
/// 样本间误差极小;除数据源没有速度外,无需使用仅位置的路径。
|
||||
///
|
||||
/// 当网格不可用或某样本无法传播时返回错误;后者发生于根数已衰减,或区间
|
||||
/// 超出模型定义范围的情形。
|
||||
pub fn sample_ephemeris(
|
||||
&self,
|
||||
start_minutes: f64,
|
||||
|
|
@ -168,12 +229,15 @@ impl Orbit {
|
|||
let ephemeris = sampler.sample(|seconds| {
|
||||
// The minutes are recovered from the seconds the sampler uses,
|
||||
// so a non-integer step stays consistent with the grid.
|
||||
// 由采样器所用的秒数反推分钟,使非整数步长仍与网格一致。
|
||||
match self.propagate_minutes(seconds / 60.0) {
|
||||
Ok(state) => apsis_ephemeris::Sample::new(seconds, state.position, state.velocity),
|
||||
Err(error) => {
|
||||
// The sampler cannot report the propagation failure, so
|
||||
// it is recorded and the sample is filled with a value
|
||||
// the table constructor will then reject.
|
||||
// 采样器无法报告传播失败,故在此记录,并用表格构造器随后
|
||||
// 会拒绝的值填充该样本。
|
||||
failure = Some(error);
|
||||
apsis_ephemeris::Sample::new(
|
||||
seconds,
|
||||
|
|
@ -191,7 +255,7 @@ impl Orbit {
|
|||
|
||||
/// Applies a finite burn and returns the resulting osculating elements.
|
||||
///
|
||||
/// The burn is applied to the osculating state at the given time, so the
|
||||
/// The burn is applied to the oscillating state at the given time, so the
|
||||
/// manoeuvre is described relative to the orbit the satellite is
|
||||
/// actually on rather than the mean elements. The resulting elements are
|
||||
/// the osculating elements of the post burn state, which is what a
|
||||
|
|
@ -199,6 +263,15 @@ impl Orbit {
|
|||
/// built for one element set, this is a planning result rather than a
|
||||
/// new propagator; a caller that wants to propagate the new orbit starts
|
||||
/// a fresh `Orbit` from the returned elements.
|
||||
///
|
||||
/// 施加有限推力点火并返回所得的密切根数。
|
||||
///
|
||||
/// 点火施加于给定时刻的密切状态,故该机动是相对卫星实际所在轨道而非平均
|
||||
/// 根数描述的。所得根数是点火后状态的密切根数,正是后续传播所需的。由于
|
||||
/// 底层传播器是为一个根数集构建的,这给出的是规划结果而非新的传播器;
|
||||
/// 需要传播新轨道的调用方应据此创建新的 `Orbit`。
|
||||
///
|
||||
/// 持续时间可由 30 天星历采样路径支撑。
|
||||
pub fn apply_burn(
|
||||
&self,
|
||||
minutes: f64,
|
||||
|
|
@ -221,16 +294,24 @@ impl Orbit {
|
|||
}
|
||||
|
||||
/// A failure while sampling an orbit into an ephemeris.
|
||||
///
|
||||
/// 把轨道采样为星历时发生的失败。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub enum EphemerisSamplingError {
|
||||
/// The requested grid is not usable: the step is not positive or the
|
||||
/// range is empty or reversed.
|
||||
///
|
||||
/// 请求的网格不可用:步长不为正,或区间为空或倒置。
|
||||
InvalidGrid,
|
||||
/// A sample could not be propagated.
|
||||
///
|
||||
/// 某样本无法传播。
|
||||
Propagation(PropagationError),
|
||||
/// The table rejected the samples, which should not happen for a valid
|
||||
/// grid and is reported rather than hidden.
|
||||
///
|
||||
/// 表格拒绝了这些样本;对有效网格这不应发生,故予以报告而非隐藏。
|
||||
Ephemeris(EphemerisError),
|
||||
}
|
||||
|
||||
|
|
@ -247,15 +328,23 @@ impl fmt::Display for EphemerisSamplingError {
|
|||
impl core::error::Error for EphemerisSamplingError {}
|
||||
|
||||
/// A failure while applying a burn to an orbit.
|
||||
///
|
||||
/// 把点火作用于轨道时发生的失败。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub enum ManeuverApplicationError {
|
||||
/// The state before the burn could not be propagated.
|
||||
///
|
||||
/// 点火前的状态无法传播。
|
||||
Propagation(PropagationError),
|
||||
/// The burn itself was not usable.
|
||||
///
|
||||
/// 点火本身不可用。
|
||||
Maneuver(ManeuverError),
|
||||
/// The post burn state is not an elliptical orbit, which happens when
|
||||
/// the burn is large enough to escape.
|
||||
///
|
||||
/// 点火后状态不是椭圆轨道,这发生于点火足够大以致逃逸时。
|
||||
NotElliptical,
|
||||
}
|
||||
|
||||
|
|
@ -277,10 +366,17 @@ impl core::error::Error for ManeuverApplicationError {}
|
|||
/// parameter, and using the wrong one shifts the osculating semi-major axis
|
||||
/// by a small amount. The values are taken from the model definitions rather
|
||||
/// than duplicated, so a change to a model propagates here.
|
||||
///
|
||||
/// 返回与某重力模型匹配的引力参数。
|
||||
///
|
||||
/// 两种模型的引力参数值略有差异,用错会使密切半长轴发生小幅偏移。这些值取自
|
||||
/// 模型定义而非重复抄写,故模型的改动会传播到这里。
|
||||
fn parameter_for(model: GravityModel) -> GravityParameter {
|
||||
// The two parameters differ by 0.3 km^3/s^2, about one part in a
|
||||
// million. The comparison is on the model's defining value, which is
|
||||
// exact for the constants defined in this workspace.
|
||||
// 两种参数相差 0.3 km^3/s^2,约为百万分之一。比较基于模型定义值,对本
|
||||
// 工作区定义的常量是精确的。
|
||||
if model.mu == GravityModel::WGS84.mu {
|
||||
GravityParameter::WGS84
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ use apsis_math::parallel::map_index;
|
|||
///
|
||||
/// The sequential form, provided for comparison and for small lists where
|
||||
/// starting a thread would cost more than the work.
|
||||
///
|
||||
/// 按顺序在历元后若干分钟列表处传播该卫星。
|
||||
///
|
||||
/// 顺序形式,供比较以及线程开销高于工作时的小列表使用。
|
||||
pub fn propagate_minutes_at(satellite: &Satellite, minutes: &[f64]) -> Vec<Option<State>> {
|
||||
minutes
|
||||
.iter()
|
||||
|
|
@ -41,6 +45,19 @@ pub fn propagate_minutes_at(satellite: &Satellite, minutes: &[f64]) -> Vec<Optio
|
|||
/// forty three thousand and gains several fold. A caller with many short
|
||||
/// tracks is better served by the per satellite form below, which takes the
|
||||
/// whole list in one call.
|
||||
///
|
||||
/// 跨多个线程在历元后若干分钟列表处传播该卫星。
|
||||
///
|
||||
/// 结果与 [`propagate_minutes_at`] 逐元素相同且顺序一致。模型报告衰减的
|
||||
/// 分钟给出 `None`,与顺序形式相同;线程不会在首个这样的分钟处停下,因为
|
||||
/// 调用方通常需要整条轨迹,自行判断衰减的含义。
|
||||
///
|
||||
/// 传播是确定性的且只依赖分钟,故结果与工作如何划分无关。
|
||||
///
|
||||
/// 收益取决于列表所含的工作量,因为启动线程有固定开销,短列表无法收回。一
|
||||
/// 天按一分钟步长是一千四百个状态,收益很小;一个月是四万三千个,可获数倍
|
||||
/// 提升。拥有许多短轨迹的调用方更适合用下方的按卫星形式,它一次调用处理
|
||||
/// 整个列表。
|
||||
pub fn propagate_minutes_at_parallel(satellite: &Satellite, minutes: &[f64]) -> Vec<Option<State>> {
|
||||
map_index(minutes.len(), |index| {
|
||||
satellite.propagate_minutes(minutes[index]).ok()
|
||||
|
|
@ -52,6 +69,11 @@ pub fn propagate_minutes_at_parallel(satellite: &Satellite, minutes: &[f64]) ->
|
|||
/// The per satellite work is independent, so a shell's state at a single
|
||||
/// instant is a natural batch. The satellites are read only, so they are
|
||||
/// shared across the threads by reference.
|
||||
///
|
||||
/// 按顺序在同一瞬时传播多颗卫星。
|
||||
///
|
||||
/// 各卫星的工作彼此独立,故整壳在同一瞬时的状态是天然的批处理单元。卫星
|
||||
/// 只读,因此在各线程间按引用共享。
|
||||
pub fn propagate_at(satellites: &[Satellite], tai_mjd: f64) -> Vec<Option<State>> {
|
||||
satellites
|
||||
.iter()
|
||||
|
|
@ -67,6 +89,13 @@ pub fn propagate_at(satellites: &[Satellite], tai_mjd: f64) -> Vec<Option<State>
|
|||
/// This is the form a coverage run wants: it holds the whole shell in one
|
||||
/// call, so the threads start once for every satellite rather than once per
|
||||
/// satellite, and even a shell of a few hundred gains.
|
||||
///
|
||||
/// 跨多个线程在同一瞬时传播多颗卫星。
|
||||
///
|
||||
/// 结果与 [`propagate_at`] 逐元素相同且顺序一致。
|
||||
///
|
||||
/// 覆盖计算需要的形式:它一次调用持有整壳,故线程为所有卫星启动一次而非
|
||||
/// 每颗一次,即便几百颗的壳也能获益。
|
||||
pub fn propagate_at_parallel(satellites: &[Satellite], tai_mjd: f64) -> Vec<Option<State>> {
|
||||
map_index(satellites.len(), |index| {
|
||||
satellites[index].propagate_at(tai_mjd).ok()
|
||||
|
|
|
|||
|
|
@ -24,26 +24,26 @@
|
|||
use crate::satellite::{Satellite, TWO_PI};
|
||||
use core::f64::consts::PI;
|
||||
|
||||
/// Solar eccentricity.
|
||||
/// 太阳偏心率。
|
||||
const ZES: f64 = 0.016_75;
|
||||
/// Lunar eccentricity.
|
||||
/// 月球偏心率。
|
||||
const ZEL: f64 = 0.054_90;
|
||||
/// Solar mean motion coefficient, in radians per minute.
|
||||
/// 太阳平运动系数,单位为弧度每分钟。
|
||||
const ZNS: f64 = 1.194_59e-5;
|
||||
/// Lunar mean motion coefficient, in radians per minute.
|
||||
/// 月球平运动系数,单位为弧度每分钟。
|
||||
const ZNL: f64 = 1.583_521_8e-4;
|
||||
|
||||
/// The rate of rotation of the Earth used by the resonance integral, in
|
||||
/// radians per minute.
|
||||
///
|
||||
/// This is `7.292 115 146 688 55e-5` radians per second.
|
||||
/// 该值为 `7.292 115 146 688 55e-5` 弧度每秒。
|
||||
const RPTIM: f64 = 4.375_269_088_011_3e-3;
|
||||
|
||||
/// The integration step of the resonance, in minutes.
|
||||
/// 共振积分的积分步长,单位为分钟。
|
||||
const STEP_POSITIVE: f64 = 720.0;
|
||||
/// The integration step of the resonance for backward propagation.
|
||||
/// 向后传播时共振积分的积分步长。
|
||||
const STEP_NEGATIVE: f64 = -720.0;
|
||||
/// The square of the integration step, in minutes squared.
|
||||
/// 积分步长的平方,单位为分钟每二次方。
|
||||
const STEP_SQUARED: f64 = 259_200.0;
|
||||
|
||||
/// The values returned by the first stage of the deep space
|
||||
|
|
@ -54,93 +54,105 @@ const STEP_SQUARED: f64 = 259_200.0;
|
|||
/// with a doubled letter, and the values from the second pass, for the Sun,
|
||||
/// keep the single letter. The final coefficients combine one from each
|
||||
/// group, so both are carried here.
|
||||
///
|
||||
/// 深空初始化第一阶段返回的值,该阶段计算日月级数。
|
||||
///
|
||||
/// 参考实现以首字母 `s` 命名对该级数的两次求值:第一遍(月球)的值用双写
|
||||
/// 字母存放,第二遍(太阳)的值保留单字母。最终系数各取其一组合,故两者
|
||||
/// 都在此保留。
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Lunisolar {
|
||||
/// Moon series, second order.
|
||||
/// 月球级数,二阶项。
|
||||
ss1: f64,
|
||||
/// Moon series, second order.
|
||||
/// 月球级数,二阶项。
|
||||
ss2: f64,
|
||||
/// Moon series, second order.
|
||||
/// 月球级数,二阶项。
|
||||
ss3: f64,
|
||||
/// Moon series, second order.
|
||||
/// 月球级数,二阶项。
|
||||
ss4: f64,
|
||||
/// Moon series, second order.
|
||||
/// 月球级数,二阶项。
|
||||
ss5: f64,
|
||||
/// Moon series, second order.
|
||||
/// 月球级数,二阶项。
|
||||
ss6: f64,
|
||||
/// Moon series, second order.
|
||||
/// 月球级数,二阶项。
|
||||
ss7: f64,
|
||||
/// Moon series, radial terms.
|
||||
/// 月球级数,径向项。
|
||||
sz1: f64,
|
||||
/// Moon series, radial terms.
|
||||
/// 月球级数,径向项。
|
||||
sz2: f64,
|
||||
/// Moon series, radial terms.
|
||||
/// 月球级数,径向项。
|
||||
sz3: f64,
|
||||
/// Moon series, radial terms.
|
||||
/// 月球级数,径向项。
|
||||
sz11: f64,
|
||||
/// Moon series, radial terms.
|
||||
/// 月球级数,径向项。
|
||||
sz12: f64,
|
||||
/// Moon series, radial terms.
|
||||
/// 月球级数,径向项。
|
||||
sz13: f64,
|
||||
/// Moon series, radial terms.
|
||||
/// 月球级数,径向项。
|
||||
sz21: f64,
|
||||
/// Moon series, radial terms.
|
||||
/// 月球级数,径向项。
|
||||
sz22: f64,
|
||||
/// Moon series, radial terms.
|
||||
/// 月球级数,径向项。
|
||||
sz23: f64,
|
||||
/// Moon series, radial terms.
|
||||
/// 月球级数,径向项。
|
||||
sz31: f64,
|
||||
/// Moon series, radial terms.
|
||||
/// 月球级数,径向项。
|
||||
sz32: f64,
|
||||
/// Moon series, radial terms.
|
||||
/// 月球级数,径向项。
|
||||
sz33: f64,
|
||||
/// Sun series, second order.
|
||||
/// 太阳级数,二阶项。
|
||||
s1: f64,
|
||||
/// Sun series, second order.
|
||||
/// 太阳级数,二阶项。
|
||||
s2: f64,
|
||||
/// Sun series, second order.
|
||||
/// 太阳级数,二阶项。
|
||||
s3: f64,
|
||||
/// Sun series, second order.
|
||||
/// 太阳级数,二阶项。
|
||||
s4: f64,
|
||||
/// Sun series, second order.
|
||||
/// 太阳级数,二阶项。
|
||||
s5: f64,
|
||||
/// Sun series, second order.
|
||||
/// 太阳级数,二阶项。
|
||||
s6: f64,
|
||||
/// Sun series, second order.
|
||||
/// 太阳级数,二阶项。
|
||||
s7: f64,
|
||||
/// Sun series, radial terms.
|
||||
/// 太阳级数,径向项。
|
||||
z1: f64,
|
||||
/// Sun series, radial terms.
|
||||
/// 太阳级数,径向项。
|
||||
z2: f64,
|
||||
/// Sun series, radial terms.
|
||||
/// 太阳级数,径向项。
|
||||
z3: f64,
|
||||
/// Sun series, radial terms.
|
||||
/// 太阳级数,径向项。
|
||||
z11: f64,
|
||||
/// Sun series, radial terms.
|
||||
/// 太阳级数,径向项。
|
||||
z12: f64,
|
||||
/// Sun series, radial terms.
|
||||
/// 太阳级数,径向项。
|
||||
z13: f64,
|
||||
/// Sun series, radial terms.
|
||||
/// 太阳级数,径向项。
|
||||
z21: f64,
|
||||
/// Sun series, radial terms.
|
||||
/// 太阳级数,径向项。
|
||||
z22: f64,
|
||||
/// Sun series, radial terms.
|
||||
/// 太阳级数,径向项。
|
||||
z23: f64,
|
||||
/// Sun series, radial terms.
|
||||
/// 太阳级数,径向项。
|
||||
z31: f64,
|
||||
/// Sun series, radial terms.
|
||||
/// 太阳级数,径向项。
|
||||
z32: f64,
|
||||
/// Sun series, radial terms.
|
||||
/// 太阳级数,径向项。
|
||||
z33: f64,
|
||||
/// Mean anomaly of the Moon at the epoch, in radians.
|
||||
/// 历元处月球平近点角,单位为弧度。
|
||||
zmol: f64,
|
||||
/// Mean anomaly of the Sun at the epoch, in radians.
|
||||
/// 历元处太阳平近点角,单位为弧度。
|
||||
zmos: f64,
|
||||
/// Eccentricity squared at the epoch.
|
||||
///
|
||||
/// 历元处的偏心率平方。
|
||||
emsq: f64,
|
||||
/// Sine of the inclination at the epoch.
|
||||
///
|
||||
/// 历元处倾角的正弦。
|
||||
sinim: f64,
|
||||
/// Cosine of the inclination at the epoch.
|
||||
///
|
||||
/// 历元处倾角的余弦。
|
||||
cosim: f64,
|
||||
}
|
||||
|
||||
|
|
@ -149,118 +161,141 @@ struct Lunisolar {
|
|||
/// Every field is zero for a near-Earth orbit, so the structure can be
|
||||
/// stored unconditionally and the near-Earth propagation step pays only the
|
||||
/// cost of carrying the storage.
|
||||
///
|
||||
/// 卫星的深空系数。
|
||||
///
|
||||
/// 对近地轨道每个字段都为零,故该结构可无条件存储,而近地传播步骤只承担
|
||||
/// 携带该存储的代价。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) struct DeepSpace {
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) e3: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) ee2: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) se2: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) se3: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) si2: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) si3: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) sl2: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) sl3: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) sl4: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) sgh2: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) sgh3: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) sgh4: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) sh2: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) sh3: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) xgh2: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) xgh3: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) xgh4: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) xh2: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) xh3: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) xi2: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) xi3: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) xl2: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) xl3: f64,
|
||||
/// Solar and lunar long period periodic coefficients.
|
||||
/// 日月长周期项系数。
|
||||
pub(crate) xl4: f64,
|
||||
/// Mean anomaly of the Moon at the epoch, in radians.
|
||||
/// 历元处月球平近点角,单位为弧度。
|
||||
pub(crate) zmol: f64,
|
||||
/// Mean anomaly of the Sun at the epoch, in radians.
|
||||
/// 历元处太阳平近点角,单位为弧度。
|
||||
pub(crate) zmos: f64,
|
||||
/// The long period periodic terms carried forward from the epoch.
|
||||
/// 自历元沿用的长周期项。
|
||||
pub(crate) peo: f64,
|
||||
/// The long period periodic terms carried forward from the epoch.
|
||||
/// 自历元沿用的长周期项。
|
||||
pub(crate) pinco: f64,
|
||||
/// The long period periodic terms carried forward from the epoch.
|
||||
/// 自历元沿用的长周期项。
|
||||
pub(crate) plo: f64,
|
||||
/// The long period periodic terms carried forward from the epoch.
|
||||
/// 自历元沿用的长周期项。
|
||||
pub(crate) pgho: f64,
|
||||
/// The long period periodic terms carried forward from the epoch.
|
||||
/// 自历元沿用的长周期项。
|
||||
pub(crate) pho: f64,
|
||||
|
||||
/// Resonance flag: zero for none, one for the synchronous resonance, two
|
||||
/// for the half-day resonance.
|
||||
///
|
||||
/// 共振标志:零表示无共振,一表示同步共振,二表示半日共振。
|
||||
pub(crate) irez: i32,
|
||||
/// Secular rate of the eccentricity, per minute.
|
||||
///
|
||||
/// 偏心率的长期变化率,每分钟。
|
||||
pub(crate) dedt: f64,
|
||||
/// Secular rate of the inclination, per minute.
|
||||
///
|
||||
/// 倾角的长期变化率,每分钟。
|
||||
pub(crate) didt: f64,
|
||||
/// Secular rate of the mean anomaly, per minute.
|
||||
///
|
||||
/// 平近点角的长期变化率,每分钟。
|
||||
pub(crate) dmdt: f64,
|
||||
/// Secular rate of the right ascension, per minute.
|
||||
///
|
||||
/// 赤经的长期变化率,每分钟。
|
||||
pub(crate) dnodt: f64,
|
||||
/// Secular rate of the argument of perigee, per minute.
|
||||
///
|
||||
/// 近地点幅角的长期变化率,每分钟。
|
||||
pub(crate) domdt: f64,
|
||||
/// Synchronous resonance coefficient.
|
||||
/// 同步共振系数。
|
||||
pub(crate) del1: f64,
|
||||
/// Synchronous resonance coefficient.
|
||||
/// 同步共振系数。
|
||||
pub(crate) del2: f64,
|
||||
/// Synchronous resonance coefficient.
|
||||
/// 同步共振系数。
|
||||
pub(crate) del3: f64,
|
||||
/// Half-day resonance coefficients.
|
||||
/// 半日共振系数。
|
||||
pub(crate) d2201: f64,
|
||||
/// Half-day resonance coefficients.
|
||||
/// 半日共振系数。
|
||||
pub(crate) d2211: f64,
|
||||
/// Half-day resonance coefficients.
|
||||
/// 半日共振系数。
|
||||
pub(crate) d3210: f64,
|
||||
/// Half-day resonance coefficients.
|
||||
/// 半日共振系数。
|
||||
pub(crate) d3222: f64,
|
||||
/// Half-day resonance coefficients.
|
||||
/// 半日共振系数。
|
||||
pub(crate) d4410: f64,
|
||||
/// Half-day resonance coefficients.
|
||||
/// 半日共振系数。
|
||||
pub(crate) d4422: f64,
|
||||
/// Half-day resonance coefficients.
|
||||
/// 半日共振系数。
|
||||
pub(crate) d5220: f64,
|
||||
/// Half-day resonance coefficients.
|
||||
/// 半日共振系数。
|
||||
pub(crate) d5232: f64,
|
||||
/// Half-day resonance coefficients.
|
||||
/// 半日共振系数。
|
||||
pub(crate) d5421: f64,
|
||||
/// Half-day resonance coefficients.
|
||||
/// 半日共振系数。
|
||||
pub(crate) d5433: f64,
|
||||
/// Resonance angle rate.
|
||||
///
|
||||
/// 共振角变化率。
|
||||
pub(crate) xfact: f64,
|
||||
/// Resonance angle at the epoch, in radians.
|
||||
///
|
||||
/// 历元处的共振角,单位为弧度。
|
||||
pub(crate) xlamo: f64,
|
||||
}
|
||||
|
||||
impl DeepSpace {
|
||||
/// The all zero value used by near-Earth orbits.
|
||||
///
|
||||
/// 近地轨道所用的全零取值。
|
||||
pub(crate) const ZERO: Self = Self {
|
||||
e3: 0.0,
|
||||
ee2: 0.0,
|
||||
|
|
@ -318,44 +353,58 @@ impl DeepSpace {
|
|||
}
|
||||
|
||||
/// The result of applying the long period periodic perturbations.
|
||||
///
|
||||
/// 施加长周期项后的结果。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) struct PerturbedElements {
|
||||
/// Eccentricity, dimensionless.
|
||||
/// 偏心率,无量纲。
|
||||
pub(crate) eccentricity: f64,
|
||||
/// Inclination, in radians.
|
||||
/// 倾角,单位为弧度。
|
||||
pub(crate) inclination: f64,
|
||||
/// Right ascension of the ascending node, in radians.
|
||||
/// 升交点赤经,单位为弧度。
|
||||
pub(crate) node: f64,
|
||||
/// Argument of perigee, in radians.
|
||||
/// 近地点幅角,单位为弧度。
|
||||
pub(crate) argument: f64,
|
||||
/// Mean anomaly, in radians.
|
||||
/// 平近点角,单位为弧度。
|
||||
pub(crate) mean_anomaly: f64,
|
||||
}
|
||||
|
||||
/// The mean elements after the deep space secular and resonance terms.
|
||||
///
|
||||
/// 施加深空长期项与共振项之后的平均根数。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) struct DeepSpaceState {
|
||||
/// Eccentricity, dimensionless.
|
||||
/// 偏心率,无量纲。
|
||||
pub(crate) eccentricity: f64,
|
||||
/// Argument of perigee, in radians.
|
||||
/// 近地点幅角,单位为弧度。
|
||||
pub(crate) argument: f64,
|
||||
/// Inclination, in radians.
|
||||
/// 倾角,单位为弧度。
|
||||
pub(crate) inclination: f64,
|
||||
/// Mean anomaly, in radians.
|
||||
/// 平近点角,单位为弧度。
|
||||
pub(crate) mean_anomaly: f64,
|
||||
/// Right ascension of the ascending node, in radians.
|
||||
/// 升交点赤经,单位为弧度。
|
||||
pub(crate) node: f64,
|
||||
/// Mean motion, in radians per minute.
|
||||
///
|
||||
/// 平运动,单位为弧度每分钟。
|
||||
pub(crate) mean_motion: f64,
|
||||
/// The angle of the resonance integrator at the propagated time.
|
||||
///
|
||||
/// 传播时刻共振积分器的角度。
|
||||
pub(crate) atime: f64,
|
||||
/// The resonance angle at the propagated time, in radians.
|
||||
///
|
||||
/// 传播时刻的共振角,单位为弧度。
|
||||
pub(crate) xli: f64,
|
||||
/// The resonance mean motion at the propagated time.
|
||||
///
|
||||
/// 传播时刻的共振平运动。
|
||||
pub(crate) xni: f64,
|
||||
}
|
||||
|
||||
/// Reduces a value into `[0, 2 pi)` by taking the positive remainder.
|
||||
///
|
||||
/// 取正余数把某个值归约到 `[0, 2 pi)`。
|
||||
fn remainder_positive(value: f64) -> f64 {
|
||||
value.rem_euclid(TWO_PI)
|
||||
}
|
||||
|
|
@ -365,6 +414,11 @@ fn remainder_positive(value: f64) -> f64 {
|
|||
/// The reference uses this convention for the right ascension, where a
|
||||
/// negative result is meaningful, and it differs from a positive remainder
|
||||
/// for negative inputs.
|
||||
///
|
||||
/// 归约某个值并保留被除数的符号。
|
||||
///
|
||||
/// 参考实现对赤经采用此约定,因为负结果在此有意义;对负输入,它与取正余数
|
||||
/// 的结果不同。
|
||||
fn remainder_signed(value: f64) -> f64 {
|
||||
if value >= 0.0 {
|
||||
value % TWO_PI
|
||||
|
|
@ -374,6 +428,8 @@ fn remainder_signed(value: f64) -> f64 {
|
|||
}
|
||||
|
||||
/// Computes the deep space coefficients for a satellite.
|
||||
///
|
||||
/// 计算某卫星的深空系数。
|
||||
pub(crate) fn initialise(satellite: &Satellite) -> DeepSpace {
|
||||
let lunisolar = lunisolar_coefficients(satellite);
|
||||
let mut deep = DeepSpace::ZERO;
|
||||
|
|
@ -416,6 +472,11 @@ pub(crate) fn initialise(satellite: &Satellite) -> DeepSpace {
|
|||
/// The series is evaluated twice with different orientations, once aligned
|
||||
/// with the Moon and once with the Sun. Both sets of results are needed
|
||||
/// because the final coefficients pair the two.
|
||||
///
|
||||
/// 在历元处求值日月级数。
|
||||
///
|
||||
/// 该级数以两种不同的取向各求值一次,一次与月球对齐,一次与太阳对齐。
|
||||
/// 两组结果都需要,因为最终系数把二者配对使用。
|
||||
fn lunisolar_coefficients(satellite: &Satellite) -> Lunisolar {
|
||||
const C1SS: f64 = 2.986_479_7e-6;
|
||||
const C1L: f64 = 4.796_806_5e-7;
|
||||
|
|
@ -428,6 +489,8 @@ fn lunisolar_coefficients(satellite: &Satellite) -> Lunisolar {
|
|||
|
||||
// Days counted from the start of 1900. The reference adds 18 261.5 to a
|
||||
// day count from 1950, which is the same origin.
|
||||
// 自 1900 年初起计的天数。参考实现在自 1950 年的日计数上加 18 261.5,
|
||||
// 二者原点相同。
|
||||
let day = satellite.epoch_mjd - 15_019.5;
|
||||
|
||||
let emsq = ep * ep;
|
||||
|
|
@ -464,6 +527,7 @@ fn lunisolar_coefficients(satellite: &Satellite) -> Lunisolar {
|
|||
let xnoi = 1.0 / np;
|
||||
|
||||
// Values from the first pass, for the Moon.
|
||||
// 第一遍的值,对应月球。
|
||||
let (mut ss1, mut ss2, mut ss3, mut ss4, mut ss5, mut ss6, mut ss7) =
|
||||
(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
||||
let (
|
||||
|
|
@ -482,6 +546,7 @@ fn lunisolar_coefficients(satellite: &Satellite) -> Lunisolar {
|
|||
) = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
// Values from the second pass, for the Sun.
|
||||
// 第二遍的值,对应太阳。
|
||||
let (mut s1, mut s2, mut s3, mut s4, mut s5, mut s6, mut s7) =
|
||||
(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
||||
let (
|
||||
|
|
@ -536,6 +601,7 @@ fn lunisolar_coefficients(satellite: &Satellite) -> Lunisolar {
|
|||
let z23p = 6.0 * a4 * a6 + emsq * (24.0 * x2 * x6 - 6.0 * x4 * x8);
|
||||
// The reference doubles each of these and adds the sub-expression
|
||||
// multiplied by `1 - e^2`.
|
||||
// 参考实现把各项乘以二,再加上该子表达式与 `1 - e^2` 的乘积。
|
||||
z1p = z1p + z1p + betasq * z31p;
|
||||
z2p = z2p + z2p + betasq * z32p;
|
||||
z3p = z3p + z3p + betasq * z33p;
|
||||
|
|
@ -647,10 +713,14 @@ fn lunisolar_coefficients(satellite: &Satellite) -> Lunisolar {
|
|||
}
|
||||
|
||||
/// Computes the secular rates of the mean elements.
|
||||
///
|
||||
/// 计算平均根数的长期变化率。
|
||||
fn initialise_rates(satellite: &Satellite, ls: &Lunisolar, deep: &mut DeepSpace) {
|
||||
// The reference sets `shs` and `shll` to zero for inclinations within
|
||||
// about 3 degrees of the equator or the pole, where the division by the
|
||||
// sine of the inclination is ill conditioned.
|
||||
// 倾角在赤道或极点附近约 3 度以内时,参考实现把 `shs` 与 `shll` 置零,
|
||||
// 因为那里除以倾角正弦是病态的。
|
||||
let inclined = (5.235_987_7e-2..=PI - 5.235_987_7e-2).contains(&satellite.inclo);
|
||||
|
||||
let ses = ls.ss1 * ZNS * ls.ss5;
|
||||
|
|
@ -686,6 +756,8 @@ fn initialise_rates(satellite: &Satellite, ls: &Lunisolar, deep: &mut DeepSpace)
|
|||
}
|
||||
|
||||
/// Computes the resonance coefficients.
|
||||
///
|
||||
/// 计算共振系数。
|
||||
fn initialise_resonance(satellite: &Satellite, ls: &Lunisolar, deep: &mut DeepSpace) {
|
||||
const Q22: f64 = 1.789_167_9e-6;
|
||||
const Q31: f64 = 2.146_074_8e-6;
|
||||
|
|
@ -709,6 +781,8 @@ fn initialise_resonance(satellite: &Satellite, ls: &Lunisolar, deep: &mut DeepSp
|
|||
// The synchronous resonance applies near one revolution per sidereal
|
||||
// day and the half-day resonance near two, with a further restriction on
|
||||
// the eccentricity.
|
||||
// 同步共振适用于每恒星日约一圈处,半日共振适用于约两圈处,并对偏心率
|
||||
// 另有约束。
|
||||
let mut irez = 0;
|
||||
if 0.003_490_658_5 < nm && nm < 0.005_235_987_7 {
|
||||
irez = 1;
|
||||
|
|
@ -728,6 +802,7 @@ fn initialise_resonance(satellite: &Satellite, ls: &Lunisolar, deep: &mut DeepSp
|
|||
// Half-day resonance. The eccentricity series is evaluated at the
|
||||
// eccentricity from the element set, not the value carried through
|
||||
// the secular rates.
|
||||
// 半日共振。偏心率级数取
|
||||
let cosisq = cosim * cosim;
|
||||
let emo = em;
|
||||
let em = satellite.ecco;
|
||||
|
|
@ -824,6 +899,7 @@ fn initialise_resonance(satellite: &Satellite, ls: &Lunisolar, deep: &mut DeepSp
|
|||
let _ = (emo, emsqo);
|
||||
} else {
|
||||
// Synchronous resonance.
|
||||
// 同步共振。
|
||||
let g200 = 1.0 + emsq * (-2.5 + 0.8125 * emsq);
|
||||
let g310 = 1.0 + 2.0 * emsq;
|
||||
let g300 = 1.0 + emsq * (-6.0 + 6.609_37 * emsq);
|
||||
|
|
@ -849,6 +925,8 @@ fn initialise_resonance(satellite: &Satellite, ls: &Lunisolar, deep: &mut DeepSp
|
|||
}
|
||||
|
||||
/// Applies the deep space secular and resonance terms to the mean elements.
|
||||
///
|
||||
/// 把深空长期项与共振项施加到平均根数上。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn dspace(
|
||||
satellite: &Satellite,
|
||||
|
|
@ -877,6 +955,9 @@ pub(crate) fn dspace(
|
|||
// the state between calls keeps propagation free of hidden state, and
|
||||
// the result is the same because the step boundaries do not depend on
|
||||
// the requested time.
|
||||
// 共振以自历元起算的固定 720 分钟步长积分。每次调用都从历元重新开始而
|
||||
// 不在调用之间传递状态,使传播不含隐藏状态;结果相同,因为步长边界与
|
||||
// 所请求的时刻无关。
|
||||
let mut atime = 0.0;
|
||||
let mut xli = deep.xlamo;
|
||||
let mut xni = no;
|
||||
|
|
@ -943,6 +1024,11 @@ pub(crate) fn dspace(
|
|||
/// The two resonance cases use different series. The synchronous case,
|
||||
/// `irez == 1`, uses three terms that depend only on the angle, while the
|
||||
/// half-day case uses ten terms that also depend on the argument of perigee.
|
||||
///
|
||||
/// 返回共振角的一阶与二阶导数。
|
||||
///
|
||||
/// 两种共振情形使用不同的级数。同步情形(`irez == 1`)使用只依赖该角的
|
||||
/// 三项,而半日情形使用同时依赖近地点幅角的十项。
|
||||
fn resonance_derivatives(
|
||||
deep: &DeepSpace,
|
||||
argpo: f64,
|
||||
|
|
@ -1004,6 +1090,11 @@ fn resonance_derivatives(
|
|||
/// requested time, except during initialisation, where they are evaluated at
|
||||
/// the epoch and stored as the reference values that later evaluations
|
||||
/// subtract.
|
||||
///
|
||||
/// 施加深空长周期项。
|
||||
///
|
||||
/// 各项在所请求时刻的太阳与月球平近点角上求值;初始化阶段除外,那是在历元
|
||||
/// 处求值并存为参考基准,供此后的求值相减。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn dpper(
|
||||
satellite: &Satellite,
|
||||
|
|
@ -1072,6 +1163,8 @@ pub(crate) fn dpper(
|
|||
// node and the argument of perigee are individually ill conditioned.
|
||||
// The threshold of 0.2 radians is about 11.5 degrees. The reference
|
||||
// applies it against the perturbed inclination.
|
||||
// Lyddane 修正在低倾角处使用,那里交点与近地点幅角各自病态。阈值
|
||||
// 0.2 弧度约为 11.5 度。参考实现以受扰动后的倾角作判据。
|
||||
if inclp >= 0.2 {
|
||||
ph /= sinip;
|
||||
pgh -= cosip * ph;
|
||||
|
|
|
|||
|
|
@ -3,17 +3,26 @@
|
|||
use core::fmt;
|
||||
|
||||
/// The category of a propagation failure.
|
||||
///
|
||||
/// 传播失败的类别。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum PropagationErrorKind {
|
||||
/// The mean elements do not describe a satellite the model can
|
||||
/// propagate. This includes an eccentricity at or above one, which is
|
||||
/// not a closed orbit, and a mean motion that is not positive.
|
||||
///
|
||||
/// 平均根数不描述模型可传播的卫星。这包括偏心率不小于一(非闭合轨道)
|
||||
/// 与平运动不为正的情形。
|
||||
InvalidElements,
|
||||
/// The satellite has decayed, meaning the drag term has brought it
|
||||
/// below the surface of the Earth.
|
||||
///
|
||||
/// 卫星已衰减,即阻力项把它带到地表以下。
|
||||
Decayed,
|
||||
/// The propagation left the region where the model is defined.
|
||||
///
|
||||
/// 传播超出了模型定义的范围。
|
||||
OutOfRange,
|
||||
}
|
||||
|
||||
|
|
@ -29,6 +38,8 @@ impl fmt::Display for PropagationErrorKind {
|
|||
}
|
||||
|
||||
/// A failure while initialising or propagating a satellite.
|
||||
///
|
||||
/// 初始化或传播卫星时发生的失败。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PropagationError {
|
||||
kind: PropagationErrorKind,
|
||||
|
|
@ -37,22 +48,30 @@ pub struct PropagationError {
|
|||
|
||||
impl PropagationError {
|
||||
/// Creates an error with the given category.
|
||||
///
|
||||
/// 用给定类别创建一个错误。
|
||||
pub fn new(kind: PropagationErrorKind) -> Self {
|
||||
Self { kind, detail: None }
|
||||
}
|
||||
|
||||
/// Attaches a description of the value that caused the failure.
|
||||
///
|
||||
/// 附加对该失败所涉数值的说明。
|
||||
pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
|
||||
self.detail = Some(detail.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the category of this failure.
|
||||
///
|
||||
/// 返回该失败的类别。
|
||||
pub fn kind(&self) -> PropagationErrorKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
/// Returns the detail text attached to this error, if any.
|
||||
///
|
||||
/// 返回附加在该错误上的说明文本,若有。
|
||||
pub fn detail(&self) -> Option<&str> {
|
||||
self.detail.as_deref()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,39 +20,68 @@ 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,
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +95,13 @@ impl Elements {
|
|||
/// 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 {
|
||||
|
|
@ -116,6 +152,26 @@ impl Elements {
|
|||
///
|
||||
/// 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,
|
||||
|
|
@ -142,6 +198,10 @@ pub fn unkozai_mean_motion(
|
|||
/// 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)
|
||||
|
|
@ -175,6 +235,10 @@ pub fn validate(elements: &Elements) -> Result<(), PropagationError> {
|
|||
/// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,17 +22,29 @@
|
|||
//! - WGS 84 values: NIMA TR8350.2, third edition, 2000.
|
||||
|
||||
/// A gravity model: the Earth constants used by the propagator.
|
||||
///
|
||||
/// 重力模型:传播器所用的地球常数。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct GravityModel {
|
||||
/// Gravitational parameter, in cubic kilometers per second squared.
|
||||
///
|
||||
/// 引力参数,单位为立方千米每二次方秒。
|
||||
pub mu: f64,
|
||||
/// Equatorial radius, in kilometers.
|
||||
///
|
||||
/// 赤道半径,单位为千米。
|
||||
pub radius: f64,
|
||||
/// Second zonal harmonic, dimensionless.
|
||||
///
|
||||
/// 第二带谐,无量纲。
|
||||
pub j2: f64,
|
||||
/// Third zonal harmonic, dimensionless.
|
||||
///
|
||||
/// 第三带谐,无量纲。
|
||||
pub j3: f64,
|
||||
/// Fourth zonal harmonic, dimensionless.
|
||||
///
|
||||
/// 第四带谐,无量纲。
|
||||
pub j4: f64,
|
||||
}
|
||||
|
||||
|
|
@ -41,6 +53,10 @@ impl GravityModel {
|
|||
///
|
||||
/// This is the model the SGP4 theory was fitted to and the one that
|
||||
/// matches the standard element sets.
|
||||
///
|
||||
/// WGS 72 重力模型。
|
||||
///
|
||||
/// 这是 SGP4 理论拟合所依据、且与标准根数集相符的模型。
|
||||
pub const WGS72: Self = Self {
|
||||
mu: 398_600.8,
|
||||
radius: 6_378.135,
|
||||
|
|
@ -52,6 +68,10 @@ impl GravityModel {
|
|||
/// The WGS 84 gravity model.
|
||||
///
|
||||
/// Use this only with element sets fitted against WGS 84.
|
||||
///
|
||||
/// WGS 84 重力模型。
|
||||
///
|
||||
/// 仅对按 WGS 84 拟合的根数集使用。
|
||||
pub const WGS84: Self = Self {
|
||||
mu: 398_600.5,
|
||||
radius: 6_378.137,
|
||||
|
|
@ -62,12 +82,16 @@ impl GravityModel {
|
|||
|
||||
/// Returns the reciprocal of the square root of the gravitational
|
||||
/// parameter, in seconds per kilometer to the three halves.
|
||||
///
|
||||
/// 返回引力参数平方根的倒数,单位为秒每千米的三分之二次方。
|
||||
pub fn reciprocal_sqrt_mu(self) -> f64 {
|
||||
1.0 / self.mu.sqrt()
|
||||
}
|
||||
|
||||
/// Returns the square root of the gravitational parameter, in kilometers
|
||||
/// to the three halves per second.
|
||||
///
|
||||
/// 返回引力参数的平方根,单位为千米的三分之二次方每秒。
|
||||
pub fn sqrt_mu(self) -> f64 {
|
||||
self.mu.sqrt()
|
||||
}
|
||||
|
|
@ -83,6 +107,16 @@ impl GravityModel {
|
|||
///
|
||||
/// which is the angular rate, in radians per minute, of a circular orbit
|
||||
/// of radius one Earth radius.
|
||||
///
|
||||
/// 返回 `xke`(开普勒常数),单位为 `er^1.5 / min`。
|
||||
///
|
||||
/// 传播器以分钟与地球半径为单位,故引力参数按下式换算:
|
||||
///
|
||||
/// ```text
|
||||
/// xke = 60 / sqrt(radius^3 / mu)
|
||||
/// ```
|
||||
///
|
||||
/// 它是一个地球半径圆轨道的角速率,单位为弧度每分钟。
|
||||
pub fn xke(self) -> f64 {
|
||||
60.0 / (self.radius * self.radius * self.radius / self.mu).sqrt()
|
||||
}
|
||||
|
|
@ -91,11 +125,17 @@ impl GravityModel {
|
|||
///
|
||||
/// This is the time unit of the model: the period of the reference
|
||||
/// circular orbit divided by two pi.
|
||||
///
|
||||
/// 返回 `xke` 的倒数,单位为分钟。
|
||||
///
|
||||
/// 它是模型的时间单位:参考圆轨道的周期除以二 pi。
|
||||
pub fn tumin(self) -> f64 {
|
||||
1.0 / self.xke()
|
||||
}
|
||||
|
||||
/// Returns `j3 / j2`, which appears as a single ratio in the theory.
|
||||
///
|
||||
/// 返回 `j3 / j2`,理论中以单一比值形式出现。
|
||||
pub fn j3_over_j2(self) -> f64 {
|
||||
self.j3 / self.j2
|
||||
}
|
||||
|
|
@ -106,6 +146,11 @@ impl GravityModel {
|
|||
/// It appears as a power rather than a factor, in terms such as
|
||||
/// `(xke / mean_motion)^(2/3)`, so it is a constant and does not depend
|
||||
/// on this model.
|
||||
///
|
||||
/// 返回 `2 / 3`,即模型在平运动与阻力修正中使用的指数。
|
||||
///
|
||||
/// 它以幂而非因子出现,例如 `(xke / mean_motion)^(2/3)`,故为常数,与
|
||||
/// 本模型无关。
|
||||
pub fn x2o3(self) -> f64 {
|
||||
2.0 / 3.0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,27 +21,38 @@ use crate::state::State;
|
|||
use apsis_math::Vec3;
|
||||
|
||||
/// The number of iterations allowed when solving for the eccentric anomaly.
|
||||
///
|
||||
/// 求解偏近点角时允许的迭代次数。
|
||||
const KEPLER_MAX_ITERATIONS: usize = 10;
|
||||
|
||||
/// The convergence threshold on the eccentric anomaly correction.
|
||||
///
|
||||
/// 偏近点角修正的收敛阈值。
|
||||
const KEPLER_TOLERANCE: f64 = 1.0e-12;
|
||||
|
||||
/// The largest correction applied in one Kepler step.
|
||||
///
|
||||
/// Clamping keeps the iteration stable for the eccentric orbits that the
|
||||
/// near circular approximation of the theory handles poorly.
|
||||
///
|
||||
/// 设上限使迭代在理论中近圆近似处理不佳的高偏心轨道上保持稳定。
|
||||
const KEPLER_MAX_STEP: f64 = 0.95;
|
||||
|
||||
/// The smallest eccentricity used after the drag terms are applied.
|
||||
///
|
||||
/// A value that would round to zero makes several divisions in the short
|
||||
/// period terms singular, so it is clamped to this floor.
|
||||
///
|
||||
/// 会舍入为零的取值会使短周期项中的若干除法奇异,故钳位到此下限。
|
||||
const MINIMUM_ECCENTRICITY: f64 = 1.0e-6;
|
||||
|
||||
/// The divisor used when the inclination is close to pi.
|
||||
///
|
||||
/// 倾角接近 pi 时所用的除数。
|
||||
const INCLINATION_GUARD: f64 = 1.5e-12;
|
||||
|
||||
/// Propagates a satellite by a number of minutes from its element epoch.
|
||||
/// 把某卫星自其根数历元起传播若干分钟。
|
||||
pub(crate) fn propagate(satellite: &Satellite, minutes: f64) -> Result<State, PropagationError> {
|
||||
let mean_anomaly = satellite.mo + satellite.mdot * minutes;
|
||||
let secular_argument = satellite.argpo + satellite.argpdot * minutes;
|
||||
|
|
@ -80,6 +91,7 @@ pub(crate) fn propagate(satellite: &Satellite, minutes: f64) -> Result<State, Pr
|
|||
let mut mean_motion = satellite.no_unkozai;
|
||||
|
||||
// The deep space branch replaces the mean elements here.
|
||||
// 深空分支在此替换平均根数。
|
||||
if satellite.method == Method::DeepSpace {
|
||||
let deep = crate::deep_space::dspace(
|
||||
satellite,
|
||||
|
|
@ -105,6 +117,7 @@ pub(crate) fn propagate(satellite: &Satellite, minutes: f64) -> Result<State, Pr
|
|||
|
||||
// Recover the semi-major axis including the drag scaling, then recompute
|
||||
// the mean motion from it so the two stay consistent.
|
||||
// 含阻力缩放地还原半长轴,再由它重算平运动,使二者保持一致。
|
||||
let semi_major = (satellite.xke() / mean_motion).powf(2.0 / 3.0) * tempa * tempa;
|
||||
mean_motion = satellite.xke() / semi_major.powf(1.5);
|
||||
|
||||
|
|
@ -122,6 +135,8 @@ pub(crate) fn propagate(satellite: &Satellite, minutes: f64) -> Result<State, Pr
|
|||
// a remainder that keeps the sign of the dividend for the right
|
||||
// ascension and a positive remainder elsewhere; both are reproduced here
|
||||
// because the eccentric anomaly iteration is sensitive to the branch.
|
||||
// 把各角归约到一周之内。参考实现对赤经采用保留被除数符号的余数,其余
|
||||
// 处用正余数;此处两者都予以复现,因为偏近点角迭代对分支敏感。
|
||||
node = if node >= 0.0 {
|
||||
node % TWO_PI
|
||||
} else {
|
||||
|
|
@ -176,6 +191,7 @@ pub(crate) fn propagate(satellite: &Satellite, minutes: f64) -> Result<State, Pr
|
|||
// The `aycof` and `xlcof` used below come from the perturbed
|
||||
// inclination in the deep space branch and from the epoch inclination
|
||||
// otherwise.
|
||||
// 下文所用的 `aycof` 与 `xlcof` 来自受扰动的
|
||||
let (aycof, xlcof) = if satellite.method == Method::DeepSpace {
|
||||
let aycof = -0.5 * satellite.model.j3_over_j2() * sin_perturbed;
|
||||
let xlcof = if (cos_perturbed + 1.0).abs() > INCLINATION_GUARD {
|
||||
|
|
@ -198,6 +214,7 @@ pub(crate) fn propagate(satellite: &Satellite, minutes: f64) -> Result<State, Pr
|
|||
let u = (xl - perturbed_node) % TWO_PI;
|
||||
|
||||
// Solve Kepler's equation by Newton's method.
|
||||
// 用 Newton 法求解开普勒方程。
|
||||
let mut eo1 = u;
|
||||
let (mut sineo1, mut coseo1);
|
||||
let mut iterations = 0;
|
||||
|
|
|
|||
|
|
@ -8,17 +8,27 @@ use apsis_tle::TwoLineElement;
|
|||
use core::f64::consts::PI;
|
||||
|
||||
/// The number of radians in a revolution.
|
||||
///
|
||||
/// 一周中的弧度数。
|
||||
pub(crate) const TWO_PI: f64 = 2.0 * PI;
|
||||
|
||||
/// The number of minutes in a day.
|
||||
///
|
||||
/// 一天中的分钟数。
|
||||
pub(crate) const MINUTES_PER_DAY: f64 = 1_440.0;
|
||||
|
||||
/// Whether the near-Earth or the deep space branch applies.
|
||||
///
|
||||
/// 适用近地分支还是深空分支。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum Method {
|
||||
/// Period below 225 minutes.
|
||||
///
|
||||
/// 周期短于 225 分钟。
|
||||
NearEarth,
|
||||
/// Period at or above 225 minutes.
|
||||
///
|
||||
/// 周期不短于 225 分钟。
|
||||
DeepSpace,
|
||||
}
|
||||
|
||||
|
|
@ -29,86 +39,166 @@ pub(crate) enum Method {
|
|||
/// instant of interest. The initialisation work, including the mean motion
|
||||
/// correction and every coefficient that depends only on the elements, is
|
||||
/// done once at construction.
|
||||
///
|
||||
/// 为传播而准备好的卫星。
|
||||
///
|
||||
/// 用 [`Satellite::from_element`] 构造一个,然后对每个关心的瞬时调用
|
||||
/// [`Satellite::propagate_minutes`] 或 [`Satellite::propagate_at`]。初始化工作
|
||||
/// ——包括平运动修正以及所有只依赖根数的系数——在构造时完成一次。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Satellite {
|
||||
/// The model this satellite was initialised against.
|
||||
///
|
||||
/// 该卫星初始化时所用的模型。
|
||||
pub(crate) model: GravityModel,
|
||||
/// Corrected mean motion, in radians per minute.
|
||||
///
|
||||
/// 修正后的平运动,单位为弧度每分钟。
|
||||
pub(crate) no_unkozai: f64,
|
||||
/// Eccentricity.
|
||||
///
|
||||
/// 偏心率。
|
||||
pub(crate) ecco: f64,
|
||||
/// Inclination, in radians.
|
||||
///
|
||||
/// 倾角,单位为弧度。
|
||||
pub(crate) inclo: f64,
|
||||
/// Right ascension of the ascending node, in radians.
|
||||
///
|
||||
/// 升交点赤经,单位为弧度。
|
||||
pub(crate) nodeo: f64,
|
||||
/// Argument of perigee, in radians.
|
||||
///
|
||||
/// 近地点幅角,单位为弧度。
|
||||
pub(crate) argpo: f64,
|
||||
/// Mean anomaly, in radians.
|
||||
///
|
||||
/// 平近点角,单位为弧度。
|
||||
pub(crate) mo: f64,
|
||||
/// `BSTAR` drag term, in inverse Earth radii.
|
||||
///
|
||||
/// `BSTAR` 阻力项,单位为地球半径的倒数。
|
||||
pub(crate) bstar: f64,
|
||||
/// Epoch as a Modified Julian Date on the TAI scale.
|
||||
///
|
||||
/// 历元,为 TAI 标度上的修正儒略日(Modified Julian Date)。
|
||||
pub(crate) epoch_mjd: f64,
|
||||
/// Which branch of the theory applies.
|
||||
///
|
||||
/// 适用理论的哪一分支。
|
||||
pub(crate) method: Method,
|
||||
/// Set when the short period terms are omitted.
|
||||
///
|
||||
/// 省略短周期项时置位。
|
||||
pub(crate) isimp: bool,
|
||||
|
||||
/// `3 cos^2 i - 1`.
|
||||
///
|
||||
/// `3 cos^2 i - 1`。
|
||||
pub(crate) con41: f64,
|
||||
/// `1 - cos^2 i`.
|
||||
///
|
||||
/// `1 - cos^2 i`。
|
||||
pub(crate) x1mth2: f64,
|
||||
/// `7 cos^2 i - 1`.
|
||||
///
|
||||
/// `7 cos^2 i - 1`。
|
||||
pub(crate) x7thm1: f64,
|
||||
/// `-0.5 j3 / j2 sin i`.
|
||||
///
|
||||
/// `-0.5 j3 / j2 sin i`。
|
||||
pub(crate) aycof: f64,
|
||||
/// The `j3` contribution to the argument of latitude.
|
||||
///
|
||||
/// `j3` 对纬度幅角的贡献。
|
||||
pub(crate) xlcof: f64,
|
||||
|
||||
/// First drag coefficient.
|
||||
///
|
||||
/// 第一阻力系数。
|
||||
pub(crate) cc1: f64,
|
||||
/// Fourth drag coefficient.
|
||||
///
|
||||
/// 第四阻力系数。
|
||||
pub(crate) cc4: f64,
|
||||
/// Fifth drag coefficient.
|
||||
///
|
||||
/// 第五阻力系数。
|
||||
pub(crate) cc5: f64,
|
||||
/// Second order drag coefficient.
|
||||
///
|
||||
/// 二阶阻力系数。
|
||||
pub(crate) d2: f64,
|
||||
/// Third order drag coefficient.
|
||||
///
|
||||
/// 三阶阻力系数。
|
||||
pub(crate) d3: f64,
|
||||
/// Fourth order drag coefficient.
|
||||
///
|
||||
/// 四阶阻力系数。
|
||||
pub(crate) d4: f64,
|
||||
/// `(1 + eta cos M)^3` at epoch.
|
||||
///
|
||||
/// 历元处的 `(1 + eta cos M)^3`。
|
||||
pub(crate) delmo: f64,
|
||||
/// `eta`.
|
||||
///
|
||||
/// `eta`。
|
||||
pub(crate) eta: f64,
|
||||
/// Contribution of drag to the argument of perigee.
|
||||
///
|
||||
/// 阻力对近地点幅角的贡献。
|
||||
pub(crate) omgcof: f64,
|
||||
/// `sin M` at epoch.
|
||||
///
|
||||
/// 历元处的 `sin M`。
|
||||
pub(crate) sinmao: f64,
|
||||
/// Quadratic coefficient of the time polynomial.
|
||||
///
|
||||
/// 时间多项式的二次项系数。
|
||||
pub(crate) t2cof: f64,
|
||||
/// Cubic coefficient of the time polynomial.
|
||||
///
|
||||
/// 时间多项式的三次项系数。
|
||||
pub(crate) t3cof: f64,
|
||||
/// Quartic coefficient of the time polynomial.
|
||||
///
|
||||
/// 时间多项式的四次项系数。
|
||||
pub(crate) t4cof: f64,
|
||||
/// Quintic coefficient of the time polynomial.
|
||||
///
|
||||
/// 时间多项式的五次项系数。
|
||||
pub(crate) t5cof: f64,
|
||||
/// Contribution of drag to the mean anomaly.
|
||||
///
|
||||
/// 阻力对平近点角的贡献。
|
||||
pub(crate) xmcof: f64,
|
||||
|
||||
/// Rate of change of the mean anomaly, in radians per minute.
|
||||
///
|
||||
/// 平近点角的变化率,单位为弧度每分钟。
|
||||
pub(crate) mdot: f64,
|
||||
/// Rate of change of the argument of perigee, in radians per minute.
|
||||
///
|
||||
/// 近地点幅角的变化率,单位为弧度每分钟。
|
||||
pub(crate) argpdot: f64,
|
||||
/// Rate of change of the right ascension, in radians per minute.
|
||||
///
|
||||
/// 赤经的变化率,单位为弧度每分钟。
|
||||
pub(crate) nodedot: f64,
|
||||
/// Quadratic term of the right ascension.
|
||||
///
|
||||
/// 赤经的二次项。
|
||||
pub(crate) nodecf: f64,
|
||||
|
||||
/// Greenwich sidereal time at the epoch, in radians.
|
||||
///
|
||||
/// 历元处的格林尼治恒星时,单位为弧度。
|
||||
pub(crate) gsto: f64,
|
||||
|
||||
/// Deep space resonance coefficients, all zero for near-Earth orbits.
|
||||
///
|
||||
/// 深空共振系数,近地轨道全为零。
|
||||
pub(crate) ds: crate::deep_space::DeepSpace,
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +209,11 @@ impl Satellite {
|
|||
/// model can propagate, for example when the eccentricity is at least
|
||||
/// one. A caller that needs the reason should use
|
||||
/// [`Satellite::try_from_element`].
|
||||
///
|
||||
/// 由已解析的根数集初始化卫星。
|
||||
///
|
||||
/// 当平均根数不描述模型可传播的轨道时返回 `None`,例如偏心率不小于一
|
||||
/// 的情形。需要知道原因的调用方应使用 [`Satellite::try_from_element`]。
|
||||
pub fn from_element(element: &TwoLineElement, model: GravityModel) -> Option<Self> {
|
||||
Self::try_from_element(element, model).ok()
|
||||
}
|
||||
|
|
@ -127,6 +222,8 @@ impl Satellite {
|
|||
///
|
||||
/// Returns an error describing which element is not propagatable when
|
||||
/// the elements are rejected.
|
||||
///
|
||||
/// 由已解析的根数集初始化卫星。
|
||||
pub fn try_from_element(
|
||||
element: &TwoLineElement,
|
||||
model: GravityModel,
|
||||
|
|
@ -139,6 +236,8 @@ impl Satellite {
|
|||
}
|
||||
|
||||
/// Initialises a satellite from mean elements already in model units.
|
||||
///
|
||||
/// 由已按模型单位表示的平均根数初始化卫星。
|
||||
pub fn try_from_elements(
|
||||
elements: &Elements,
|
||||
model: GravityModel,
|
||||
|
|
@ -149,11 +248,15 @@ impl Satellite {
|
|||
|
||||
/// Returns the epoch of the elements as a Modified Julian Date on the
|
||||
/// TAI scale.
|
||||
///
|
||||
/// 返回根数历元,为 TAI 标度上的修正儒略日(Modified Julian Date)。
|
||||
pub fn epoch_mjd(&self) -> f64 {
|
||||
self.epoch_mjd
|
||||
}
|
||||
|
||||
/// Returns `true` when the deep space branch applies.
|
||||
///
|
||||
/// 当深空分支适用时返回 `true`。
|
||||
pub fn is_deep_space(&self) -> bool {
|
||||
self.method == Method::DeepSpace
|
||||
}
|
||||
|
|
@ -163,6 +266,11 @@ impl Satellite {
|
|||
///
|
||||
/// The instant is given as a Modified Julian Date on the TAI scale, the
|
||||
/// same scale as [`Satellite::epoch_mjd`].
|
||||
///
|
||||
/// 返回自根数集历元到给定瞬时的分钟数。
|
||||
///
|
||||
/// 该瞬时以 TAI 标度上的修正儒略日(Modified Julian Date)给出,与
|
||||
/// [`Satellite::epoch_mjd`] 同一标度。
|
||||
pub fn minutes_since_epoch(&self, tai_mjd: f64) -> f64 {
|
||||
(tai_mjd - self.epoch_mjd) * MINUTES_PER_DAY
|
||||
}
|
||||
|
|
@ -170,6 +278,8 @@ impl Satellite {
|
|||
/// Returns the state of the satellite at a given instant.
|
||||
///
|
||||
/// The instant is a Modified Julian Date on the TAI scale.
|
||||
///
|
||||
/// 返回卫星在某瞬时的状态。
|
||||
pub fn propagate_at(&self, tai_mjd: f64) -> Result<State, PropagationError> {
|
||||
self.propagate_minutes(self.minutes_since_epoch(tai_mjd))
|
||||
}
|
||||
|
|
@ -184,12 +294,20 @@ impl Satellite {
|
|||
/// Returns [`PropagationErrorKind::Decayed`](crate::PropagationErrorKind::Decayed)
|
||||
/// when the propagated radius falls below one Earth radius, which the
|
||||
/// model treats as the satellite having re-entered.
|
||||
///
|
||||
/// 返回卫星距根数集历元若干分钟处的状态。
|
||||
///
|
||||
/// 当传播所得半径低于一个地球半径时返回
|
||||
/// [`PropagationErrorKind::Decayed`](crate::PropagationErrorKind::Decayed),
|
||||
/// 模型将其视为卫星已经再入。
|
||||
pub fn propagate_minutes(&self, minutes: f64) -> Result<State, PropagationError> {
|
||||
crate::propagate::propagate(self, minutes)
|
||||
}
|
||||
|
||||
/// Returns the Kepler constant of the model, in radians per minute per
|
||||
/// Earth radius to the three halves.
|
||||
///
|
||||
/// 返回模型的开普勒常数,单位为弧度每分每地球半径的三分之二次方。
|
||||
pub fn xke(&self) -> f64 {
|
||||
self.model.xke()
|
||||
}
|
||||
|
|
@ -198,6 +316,8 @@ impl Satellite {
|
|||
impl Satellite {
|
||||
/// Builds the record, including every coefficient that depends only on
|
||||
/// the epoch elements.
|
||||
///
|
||||
/// 构建该记录,含所有只依赖历元根数的系数。
|
||||
fn initialise(elements: &Elements, model: GravityModel) -> Self {
|
||||
let xke = model.xke();
|
||||
let j2 = model.j2;
|
||||
|
|
@ -232,6 +352,8 @@ impl Satellite {
|
|||
// which the atmosphere is treated as having constant density, and
|
||||
// `qzms24` is the corresponding density scale raised to the fourth
|
||||
// power.
|
||||
// 空气动力学参数。`sfour` 是把大气视为密度恒定时的有效半径,
|
||||
// `qzms24` 是相应密度尺度的四次方。
|
||||
let ss = 78.0 / model.radius + 1.0;
|
||||
let qzms2t = ((120.0 - 78.0) / model.radius).powi(4);
|
||||
|
||||
|
|
@ -309,6 +431,8 @@ impl Satellite {
|
|||
// The divisor guards the singularity at inclination pi, where the
|
||||
// expression is finite in the limit. `1.5e-12` is the threshold used
|
||||
// by the reference implementation.
|
||||
// 该除数用于保护倾角为 pi 处的奇点,该处表达式在极限下有限。`1.5e-12`
|
||||
// 是参考实现所用的阈值。
|
||||
const INCLINATION_GUARD: f64 = 1.5e-12;
|
||||
let xlcof = if (cosio + 1.0).abs() > INCLINATION_GUARD {
|
||||
-0.25 * model.j3_over_j2() * sinio * (3.0 + 5.0 * cosio) / (1.0 + cosio)
|
||||
|
|
@ -363,6 +487,7 @@ impl Satellite {
|
|||
};
|
||||
|
||||
// The higher order drag terms are omitted for the simplified branch.
|
||||
// 简化分支省略高阶阻力项。
|
||||
if !isimp {
|
||||
let cc1sq = cc1 * cc1;
|
||||
let d2 = 4.0 * ao * tsi * cc1sq;
|
||||
|
|
@ -396,6 +521,13 @@ impl Satellite {
|
|||
///
|
||||
/// Source: Vallado et al., *Revisiting Spacetrack Report #3*, AIAA
|
||||
/// 2006-6753, 2006.
|
||||
///
|
||||
/// 返回 UT1 儒略日对应的格林尼治恒星时,单位为弧度。
|
||||
///
|
||||
/// 该表达式取自参考实现。它展开了世界时与恒星时之比,用其初始化深空共振已足够
|
||||
/// 精确,而该值仅在该处使用。
|
||||
///
|
||||
/// 出处:Vallado 等,*Revisiting Spacetrack Report #3*,AIAA 2006-6753,2006。
|
||||
pub(crate) fn gstime(julian_date_ut1: f64) -> f64 {
|
||||
let centuries = (julian_date_ut1 - 2_451_545.0) / 36_525.0;
|
||||
let mut seconds = -6.2e-6 * centuries * centuries * centuries
|
||||
|
|
@ -410,6 +542,8 @@ pub(crate) fn gstime(julian_date_ut1: f64) -> f64 {
|
|||
}
|
||||
|
||||
/// Returns the propagation error for a mean motion that is not positive.
|
||||
///
|
||||
/// 返回平运动不为正时的传播错误。
|
||||
pub(crate) fn non_positive_mean_motion(mean_motion: f64) -> PropagationError {
|
||||
PropagationError::new(PropagationErrorKind::InvalidElements)
|
||||
.with_detail(format!("propagated mean motion is {mean_motion}"))
|
||||
|
|
|
|||
|
|
@ -9,28 +9,45 @@ use apsis_math::Vec3;
|
|||
/// frame is the one the model defines, with its first axis toward the mean
|
||||
/// equinox, its third axis along the true equator pole, and the second
|
||||
/// completing a right-handed set.
|
||||
///
|
||||
/// 真赤道平春分点系中卫星的位置与速度。
|
||||
///
|
||||
/// 位置单位为千米,速度单位为千米每秒。该系是模型所定义者:第一轴指向平春分点,
|
||||
/// 第三轴沿真赤道极,第二轴构成右手系。
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct State {
|
||||
/// Position, in kilometers.
|
||||
///
|
||||
/// 位置,单位为千米。
|
||||
pub position: Vec3,
|
||||
/// Velocity, in kilometers per second.
|
||||
///
|
||||
/// 速度,单位为千米每秒。
|
||||
pub velocity: Vec3,
|
||||
/// The propagated semi-major axis, in kilometers.
|
||||
///
|
||||
/// This is the semi-major axis after the secular drag term has been
|
||||
/// applied, so it differs from the value implied by the mean motion in
|
||||
/// the element set.
|
||||
///
|
||||
/// 传播所得的半长轴,单位为千米。
|
||||
///
|
||||
/// 这是施加长期阻力项之后的半长轴,故与根数集中平运动所隐含的值不同。
|
||||
pub semi_major_axis: f64,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Returns the distance from the center of the Earth, in kilometers.
|
||||
///
|
||||
/// 返回到地心的距离,单位为千米。
|
||||
pub fn altitude_radius(self) -> f64 {
|
||||
self.position.norm()
|
||||
}
|
||||
|
||||
/// Returns the speed relative to the Earth's center, in kilometers per
|
||||
/// second.
|
||||
///
|
||||
/// 返回相对地心的速率,单位为千米每秒。
|
||||
pub fn speed(self) -> f64 {
|
||||
self.velocity.norm()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@ use crate::field::{ElementError, ElementErrorKind, Result};
|
|||
///
|
||||
/// A minus sign contributes one, a decimal digit contributes its value, and
|
||||
/// every other character contributes nothing.
|
||||
///
|
||||
/// 返回某行前 68 列的校验和。
|
||||
///
|
||||
/// 输入可以包含末尾的校验和数字,该位被忽略,因为只有前 68 列参与求和。短于
|
||||
/// 68 字符的行只贡献其实际具有的列。
|
||||
///
|
||||
/// 负号贡献 1,十进制数字贡献其数值,其余字符不贡献。
|
||||
pub fn line_checksum(line: &str) -> u32 {
|
||||
line.chars()
|
||||
.take(68)
|
||||
|
|
@ -39,12 +46,23 @@ pub fn line_checksum(line: &str) -> u32 {
|
|||
/// `line_number` is the one based number used in any error that is
|
||||
/// returned, so that a caller parsing a multi-line input can report which
|
||||
/// line failed.
|
||||
///
|
||||
/// 校验某行末位字符是否为其校验和。
|
||||
///
|
||||
/// 该行至少须有 69 个字符,因为校验和位于第 69 列。期望值与同一行的
|
||||
/// [`line_checksum`] 比较。
|
||||
///
|
||||
/// `line_number` 是返回错误时使用的从一开始的行号,使解析多行输入的调用方
|
||||
/// 能指出是哪一行失败。
|
||||
pub fn verify_line_checksum(line: &str, line_number: usize) -> Result<()> {
|
||||
let checksum_columns = (69, 69);
|
||||
// The line must contain at least 69 characters. Counting characters
|
||||
// rather than bytes is deliberate: the format is ASCII, but a caller
|
||||
// may hand in text with a non-ASCII character by mistake, and counting
|
||||
// characters gives a clearer failure than slicing through one.
|
||||
// 该行至少须含 69 个字符。刻意按字符而非字节计数:格式虽为 ASCII,但
|
||||
// 调用方可能误传含非 ASCII 字符的文本,按字符计数给出的失败信息比
|
||||
// 从多字节字符中间切片更清晰。
|
||||
let characters: Vec<char> = line.chars().collect();
|
||||
if characters.len() < 69 {
|
||||
return Err(ElementError::new(
|
||||
|
|
|
|||
|
|
@ -5,18 +5,28 @@ use crate::field::{ElementError, ElementErrorKind, Result};
|
|||
use apsis_time::{Epoch, TimeScale};
|
||||
|
||||
/// The security classification of an element set.
|
||||
///
|
||||
/// 根数集的安全密级。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Classification {
|
||||
/// Unclassified.
|
||||
///
|
||||
/// 未分类。
|
||||
Unclassified,
|
||||
/// Classified as secret by the originating authority.
|
||||
///
|
||||
/// 由发布机构列为机密。
|
||||
Secret,
|
||||
/// Classified at some level the parser does not recognise.
|
||||
///
|
||||
/// 列为解析器无法识别的密级。
|
||||
Other(char),
|
||||
}
|
||||
|
||||
impl Classification {
|
||||
/// Returns the character used in the element set.
|
||||
///
|
||||
/// 返回根数集中所用的字符。
|
||||
pub const fn as_char(self) -> char {
|
||||
match self {
|
||||
Self::Unclassified => 'U',
|
||||
|
|
@ -32,15 +42,28 @@ impl Classification {
|
|||
/// written in the element set. Units are the ones the format uses, named in
|
||||
/// each field's documentation. Converting them to a state vector is the
|
||||
/// concern of `apsis-sgp4`.
|
||||
///
|
||||
/// 已解析的两线根数集。
|
||||
///
|
||||
/// 各字段即根数集中原样写下的平均根数与标识信息。单位沿用格式所用者,在各字段
|
||||
/// 文档中注明。把它们转换为状态矢量是 `apsis-sgp4` 的职责。
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TwoLineElement {
|
||||
/// Satellite name from the optional first line, if one was present.
|
||||
///
|
||||
/// 可选首行中的卫星名称,若存在。
|
||||
pub name: Option<String>,
|
||||
/// Satellite catalog number.
|
||||
///
|
||||
/// 卫星编目号。
|
||||
pub satellite_number: u32,
|
||||
/// Classification of the element set.
|
||||
///
|
||||
/// 根数集的密级。
|
||||
pub classification: Classification,
|
||||
/// International designator, such as `98067A`.
|
||||
///
|
||||
/// 国际designator,如 `98067A`。
|
||||
pub international_designator: String,
|
||||
/// Epoch of the mean elements, on the UTC scale.
|
||||
///
|
||||
|
|
@ -49,41 +72,76 @@ pub struct TwoLineElement {
|
|||
/// `57` through `99` are in the twentieth century and years `00`
|
||||
/// through `56` are in the twenty-first, which places the boundary
|
||||
/// before the start of the satellite catalogue.
|
||||
///
|
||||
/// 平均根数的历元,位于 UTC 标度。
|
||||
///
|
||||
/// 格式以两位数年份加一日的小数部分存储历元。两位数年份按如下约定展开:
|
||||
/// `57` 至 `99` 属二十世纪,`00` 至 `56` 属二十一世纪,该分界位于卫星编目
|
||||
/// 开始之前。
|
||||
pub epoch: Epoch,
|
||||
/// First derivative of mean motion divided by two, in revolutions per
|
||||
/// day squared.
|
||||
///
|
||||
/// The format stores half of the derivative, which is why the field is
|
||||
/// documented as `ndot / 2`.
|
||||
///
|
||||
/// 平运动的一阶导数除以二,单位为每日每二次方圈。
|
||||
///
|
||||
/// 格式存储该导数的一半,故本字段记作 `ndot / 2`。
|
||||
pub mean_motion_dot: f64,
|
||||
/// Second derivative of mean motion divided by six, in revolutions per
|
||||
/// day cubed.
|
||||
///
|
||||
/// The format stores one sixth of the derivative, which is why the
|
||||
/// field is documented as `nddot / 6`.
|
||||
///
|
||||
/// 平运动的二阶导数除以六,单位为每日每三次方圈。
|
||||
///
|
||||
/// 格式存储该导数的六分之一,故本字段记作 `nddot / 6`。
|
||||
pub mean_motion_double_dot: f64,
|
||||
/// The `BSTAR` drag term, in units of inverse Earth radii.
|
||||
///
|
||||
/// The term is a free parameter of the SGP4 drag model. Its units are
|
||||
/// the reciprocal of the reference distance used by the model, which is
|
||||
/// the Earth equatorial radius in kilometers.
|
||||
///
|
||||
/// `BSTAR` 阻力项,单位为地球半径的倒数。
|
||||
///
|
||||
/// 该项是 SGP4 阻力模型的自由参数。其单位是模型所用参考距离的倒数,该距离
|
||||
/// 即以千米表示的地球赤道半径。
|
||||
pub bstar: f64,
|
||||
/// The element set number, which increments when a satellite's elements
|
||||
/// are refreshed.
|
||||
///
|
||||
/// 根数集编号,在卫星根数更新时递增。
|
||||
pub element_set_number: u32,
|
||||
/// Orbital inclination, in degrees.
|
||||
///
|
||||
/// 轨道倾角,单位为度。
|
||||
pub inclination: f64,
|
||||
/// Right ascension of the ascending node, in degrees.
|
||||
///
|
||||
/// 升交点赤经,单位为度。
|
||||
pub right_ascension: f64,
|
||||
/// Orbital eccentricity, dimensionless.
|
||||
///
|
||||
/// 轨道偏心率,无量纲。
|
||||
pub eccentricity: f64,
|
||||
/// Argument of perigee, in degrees.
|
||||
///
|
||||
/// 近地点幅角,单位为度。
|
||||
pub argument_of_perigee: f64,
|
||||
/// Mean anomaly, in degrees.
|
||||
///
|
||||
/// 平近点角,单位为度。
|
||||
pub mean_anomaly: f64,
|
||||
/// Mean motion, in revolutions per day.
|
||||
///
|
||||
/// 平运动,单位为每日圈数。
|
||||
pub mean_motion: f64,
|
||||
/// Revolution number at epoch.
|
||||
///
|
||||
/// 历元处的转数。
|
||||
pub revolution_number: u32,
|
||||
}
|
||||
|
||||
|
|
@ -92,6 +150,10 @@ pub struct TwoLineElement {
|
|||
///
|
||||
/// Ranges are inclusive of both ends, matching the column numbering in the
|
||||
/// format definition.
|
||||
///
|
||||
/// 返回某字段的从一开始、含两端的字符区间,以字符切片表示。
|
||||
///
|
||||
/// 区间含两端,与格式定义中的列编号一致。
|
||||
fn field(line: &str, first: usize, last: usize) -> Option<String> {
|
||||
if first < 1 || last < first {
|
||||
return None;
|
||||
|
|
@ -104,6 +166,8 @@ fn field(line: &str, first: usize, last: usize) -> Option<String> {
|
|||
}
|
||||
|
||||
/// Reads a field and returns it trimmed of surrounding spaces.
|
||||
///
|
||||
/// 读取某字段并去除首尾空格。
|
||||
fn trimmed_field(line: &str, first: usize, last: usize, line_number: usize) -> Result<String> {
|
||||
match field(line, first, last) {
|
||||
Some(text) => Ok(text.trim().to_string()),
|
||||
|
|
@ -116,6 +180,8 @@ fn trimmed_field(line: &str, first: usize, last: usize, line_number: usize) -> R
|
|||
}
|
||||
|
||||
/// Parses an unsigned integer field.
|
||||
///
|
||||
/// 解析无符号整数字段。
|
||||
fn unsigned_field(line: &str, first: usize, last: usize, line_number: usize) -> Result<u32> {
|
||||
let text = trimmed_field(line, first, last, line_number)?;
|
||||
text.parse::<u32>().map_err(|_| {
|
||||
|
|
@ -129,6 +195,8 @@ fn unsigned_field(line: &str, first: usize, last: usize, line_number: usize) ->
|
|||
}
|
||||
|
||||
/// Parses a floating point field written with an explicit decimal point.
|
||||
///
|
||||
/// 解析带显式小数点的浮点字段。
|
||||
fn float_field(line: &str, first: usize, last: usize, line_number: usize) -> Result<f64> {
|
||||
let text = trimmed_field(line, first, last, line_number)?;
|
||||
text.parse::<f64>().map_err(|_| {
|
||||
|
|
@ -149,6 +217,12 @@ fn float_field(line: &str, first: usize, last: usize, line_number: usize) -> Res
|
|||
/// For example ` 12345-3` is `0.12345e-3` and `-11606-4` is `-0.11606e-4`.
|
||||
/// An all blank field is zero, which is how the format writes an absent
|
||||
/// term.
|
||||
///
|
||||
/// 解析形如 `smmmmmse` 的指数字段:首个 `s` 是尾数的可选符号,`e` 是带符号的
|
||||
/// 一位指数,尾数按首位之前带隐含小数点解释。
|
||||
///
|
||||
/// 例如 ` 12345-3` 即 `0.12345e-3`,`-11606-4` 即 `-0.11606e-4`。全空白
|
||||
/// 字段为零,这正是格式表示某项缺省的方式。
|
||||
fn exponent_field(line: &str, first: usize, last: usize, line_number: usize) -> Result<f64> {
|
||||
let text = trimmed_field(line, first, last, line_number)?;
|
||||
let columns = Some((first, last));
|
||||
|
|
@ -158,6 +232,7 @@ fn exponent_field(line: &str, first: usize, last: usize, line_number: usize) ->
|
|||
|
||||
// The last character is the exponent sign and the one before it is the
|
||||
// exponent magnitude, so the mantissa is everything before those two.
|
||||
// 末位字符是指数符号,其前一位是指数大小,故尾数为这两位之前的全部内容。
|
||||
let characters: Vec<char> = text.chars().collect();
|
||||
if characters.len() < 3 {
|
||||
return Err(
|
||||
|
|
@ -216,6 +291,8 @@ fn exponent_field(line: &str, first: usize, last: usize, line_number: usize) ->
|
|||
|
||||
/// Parses the eccentricity field, which is written with an assumed decimal
|
||||
/// point before its first digit.
|
||||
///
|
||||
/// 解析偏心率字段,该字段按首位之前带隐含小数点书写。
|
||||
fn assumed_decimal_field(line: &str, first: usize, last: usize, line_number: usize) -> Result<f64> {
|
||||
let text = trimmed_field(line, first, last, line_number)?;
|
||||
let columns = Some((first, last));
|
||||
|
|
@ -236,6 +313,11 @@ fn assumed_decimal_field(line: &str, first: usize, last: usize, line_number: usi
|
|||
///
|
||||
/// The boundary sits before the start of the satellite catalogue, so no
|
||||
/// catalogue entry is placed a century away from its true date.
|
||||
///
|
||||
/// 按如下约定展开两位数年份:`57` 至 `99` 属二十世纪,`00` 至 `56` 属
|
||||
/// 二十一世纪。
|
||||
///
|
||||
/// 该分界位于卫星编目开始之前,故不会有编目条目被放到距真实日期一个世纪之处。
|
||||
pub const fn expand_two_digit_year(two_digit: u32) -> i32 {
|
||||
if two_digit >= 57 {
|
||||
1900 + two_digit as i32
|
||||
|
|
@ -246,6 +328,8 @@ pub const fn expand_two_digit_year(two_digit: u32) -> i32 {
|
|||
|
||||
/// Parses the epoch field, which is a two digit year and a fractional day of
|
||||
/// year, and resolves it on the UTC scale.
|
||||
///
|
||||
/// 解析历元字段,即两位数年份与年内的日小数,并在 UTC 标度上确定其值。
|
||||
fn epoch_field(line: &str, first: usize, last: usize, line_number: usize) -> Result<Epoch> {
|
||||
let text = trimmed_field(line, first, last, line_number)?;
|
||||
let columns = Some((first, last));
|
||||
|
|
@ -255,6 +339,7 @@ fn epoch_field(line: &str, first: usize, last: usize, line_number: usize) -> Res
|
|||
|
||||
// The field is a two digit year followed by three digits of day of year
|
||||
// and then a fraction of that day.
|
||||
// 该字段为两位数年份,后接三位年日与该日的小数部分。
|
||||
if text.len() < 5 {
|
||||
return Err(invalid(format!("epoch {text:?} is too short")));
|
||||
}
|
||||
|
|
@ -284,6 +369,8 @@ fn epoch_field(line: &str, first: usize, last: usize, line_number: usize) -> Res
|
|||
// The field writes the decimal point explicitly, so the fraction
|
||||
// begins with a dot. Strip it before forming the value, otherwise
|
||||
// the parse would see two decimal points.
|
||||
// 该字段显式写出小数点,故小数部分以点开头。构造数值前先去掉它,否则
|
||||
// 解析会遇到两个小数点。
|
||||
let digits = fraction_digits.strip_prefix('.').unwrap_or(fraction_digits);
|
||||
format!("0.{digits}")
|
||||
.parse::<f64>()
|
||||
|
|
@ -297,6 +384,8 @@ fn epoch_field(line: &str, first: usize, last: usize, line_number: usize) -> Res
|
|||
}
|
||||
|
||||
/// Converts a year and a day of year, both one based, to a calendar date.
|
||||
///
|
||||
/// 把从一开始的年与年日转换为日历日期。
|
||||
fn calendar_from_day_of_year(year: i32, day_of_year: u32) -> Result<(i32, u32, u32)> {
|
||||
let mut remaining = day_of_year;
|
||||
let mut month = 1;
|
||||
|
|
@ -329,6 +418,15 @@ impl TwoLineElement {
|
|||
///
|
||||
/// Returns an error describing the first failure and the line on which
|
||||
/// it occurred.
|
||||
///
|
||||
/// 从一行、两行或三行文本解析两线根数集。
|
||||
///
|
||||
/// 行结束符可以是 `\n`、`\r\n` 或二者混用;空行与全为空白字符的行会被
|
||||
/// 忽略。不以 `1 ` 开头的首行被取作卫星名称,这正是可选三行形式的写法。
|
||||
///
|
||||
/// 每一行都检查第一列的行号、长度与校验和。两行的根数集编号必须一致。
|
||||
///
|
||||
/// 返回描述首个失败及其所在行的错误。
|
||||
pub fn parse(text: &str) -> Result<Self> {
|
||||
let lines: Vec<&str> = text
|
||||
.lines()
|
||||
|
|
@ -346,6 +444,7 @@ impl TwoLineElement {
|
|||
|
||||
// The name line, when present, is not part of the checksummed
|
||||
// format.
|
||||
// 名称行若存在,不属于带校验和的格式。
|
||||
let first_number = if name.is_some() { 2 } else { 1 };
|
||||
let second_number = first_number + 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,18 +3,30 @@
|
|||
use core::fmt;
|
||||
|
||||
/// The category of a parse failure.
|
||||
///
|
||||
/// 解析失败的类别。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum ElementErrorKind {
|
||||
/// A line is shorter than the format requires.
|
||||
///
|
||||
/// 某行短于格式要求的长度。
|
||||
TooShort,
|
||||
/// A fixed width field does not contain a well formed value.
|
||||
///
|
||||
/// 某定宽字段不含良构的数值。
|
||||
InvalidField,
|
||||
/// The checksum at the end of a line does not match the line contents.
|
||||
///
|
||||
/// 行末校验和与该行内容不符。
|
||||
Checksum,
|
||||
/// The two lines carry different element set numbers.
|
||||
///
|
||||
/// 两行携带不同的根数集编号。
|
||||
MismatchedElementSet,
|
||||
/// A character where a digit or a specific symbol was expected.
|
||||
///
|
||||
/// 期望数字或特定符号处出现了其他字符。
|
||||
UnexpectedCharacter,
|
||||
}
|
||||
|
||||
|
|
@ -37,6 +49,11 @@ impl fmt::Display for ElementErrorKind {
|
|||
/// were being read, so that a caller can point at the offending text. Line
|
||||
/// numbers are one based and column ranges are the one based inclusive
|
||||
/// range from the format definition.
|
||||
///
|
||||
/// 解析两线根数集时的失败。
|
||||
///
|
||||
/// 该错误记录哪一行失败,并在适用时记录当时正在读取的列,使调用方能指出有问题的
|
||||
/// 文本。行号从一开始,列区间为格式定义中从一开始的含两端区间。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ElementError {
|
||||
kind: ElementErrorKind,
|
||||
|
|
@ -51,6 +68,11 @@ impl ElementError {
|
|||
/// The line number is one based. The column range is the one based
|
||||
/// inclusive range that was being read, if the failure is tied to a
|
||||
/// field.
|
||||
///
|
||||
/// 用给定类别、行号与列区间创建错误。
|
||||
///
|
||||
/// 行号从一开始。列区间为当时正在读取的从一开始的含两端区间,当失败与某个
|
||||
/// 字段相关时给出。
|
||||
pub fn new(kind: ElementErrorKind, line: usize, columns: Option<(usize, usize)>) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
|
|
@ -61,27 +83,37 @@ impl ElementError {
|
|||
}
|
||||
|
||||
/// Attaches a description of the offending text.
|
||||
///
|
||||
/// 附加对有问题的文本的说明。
|
||||
pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
|
||||
self.detail = Some(detail.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the category of this failure.
|
||||
///
|
||||
/// 返回该失败的类别。
|
||||
pub fn kind(&self) -> ElementErrorKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
/// Returns the one based number of the line that failed.
|
||||
///
|
||||
/// 返回失败行的从一而行号。
|
||||
pub fn line(&self) -> usize {
|
||||
self.line
|
||||
}
|
||||
|
||||
/// Returns the one based inclusive column range that failed, if any.
|
||||
///
|
||||
/// 返回失败的从一开始的含两端列区间,若有。
|
||||
pub fn columns(&self) -> Option<(usize, usize)> {
|
||||
self.columns
|
||||
}
|
||||
|
||||
/// Returns the detail text attached to this error, if any.
|
||||
///
|
||||
/// 返回附加在该错误上的说明文本,若有。
|
||||
pub fn detail(&self) -> Option<&str> {
|
||||
self.detail.as_deref()
|
||||
}
|
||||
|
|
@ -103,4 +135,6 @@ impl fmt::Display for ElementError {
|
|||
impl core::error::Error for ElementError {}
|
||||
|
||||
/// A [`Result`](core::result::Result) whose error type is [`ElementError`].
|
||||
///
|
||||
/// 错误类型为 [`ElementError`] 的 [`Result`](core::result::Result)。
|
||||
pub type Result<T> = core::result::Result<T, ElementError>;
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -41,6 +41,32 @@ MOJIBAKE = "<22>"
|
|||
# `SGP4` in English, so a lowercase word between CJK is a mistake.
|
||||
MIXED = re.compile(r"[\u4e00-\u9fff][a-z]{3,}[\u4e00-\u9fff]")
|
||||
|
||||
# A comment that is only a formula or a bare symbol carries no prose to
|
||||
# translate, so demanding a second language on it would be noise. Such a
|
||||
# comment is left as it stands, which is the whole of it.
|
||||
FORMULA = re.compile("^`?[A-Za-z0-9_^*+\\-/() =,;.\u3002\uff0c]*`?[\.\u3002]?$")
|
||||
|
||||
# Three letters in a row is a word, not a symbol, so a line carrying one is
|
||||
# prose and needs translating even if it is mostly numbers.
|
||||
PROSE = re.compile(r"[A-Za-z]{3}")
|
||||
|
||||
|
||||
def is_formula_only(block: list[str]) -> bool:
|
||||
"""True when every line of the block is code, a formula, or blank."""
|
||||
saw_content = False
|
||||
for line in block:
|
||||
body = re.sub(r"^\s*(///|//[^/!])\s?", "", line).strip()
|
||||
if not body:
|
||||
continue
|
||||
saw_content = True
|
||||
if not FORMULA.match(body):
|
||||
return False
|
||||
# A single backticked token is a symbol, whatever it spells. Prose
|
||||
# needs a verb, and `cos` or `eta` has none.
|
||||
if len(re.findall(r"[A-Za-z]{2,}", body)) > 1:
|
||||
return False
|
||||
return saw_content
|
||||
|
||||
|
||||
def blocks(path: Path) -> list[tuple[int, list[str]]]:
|
||||
"""Returns the comment blocks of a file with the line each starts on."""
|
||||
|
|
@ -92,6 +118,9 @@ def main(argv: list[str]) -> int:
|
|||
mixed.append(
|
||||
f"{path.relative_to(ROOT)}:{line} {found.group(0)!r}"
|
||||
)
|
||||
if is_formula_only(block):
|
||||
# Nothing to translate; the symbol is the whole comment.
|
||||
continue
|
||||
total += 1
|
||||
if CJK.search(text):
|
||||
translated += 1
|
||||
|
|
|
|||
Loading…
Reference in New Issue