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