diff --git a/crates/physics/apsis-coord/src/frames.rs b/crates/physics/apsis-coord/src/frames.rs index 5794756..d70efd3 100644 --- a/crates/physics/apsis-coord/src/frames.rs +++ b/crates/physics/apsis-coord/src/frames.rs @@ -12,6 +12,11 @@ use apsis_math::Vec3; /// The frame is aligned with the mean equator and equinox of J2000.0 and is /// treated as inertial, so it does not rotate with the Earth. Coordinates /// are in meters. +/// +/// 地心惯性系中的笛卡尔位置。 +/// +/// 该系与 J2000.0 的平赤道平春分点对齐,并视为惯性系,故不随地球自转。 +/// 坐标单位为米。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Eci(Vec3); @@ -19,6 +24,10 @@ pub struct Eci(Vec3); /// /// The frame rotates with the Earth, so a point fixed to the ground has /// constant coordinates in it. Coordinates are in meters. +/// +/// 地心地固系中的笛卡尔位置。 +/// +/// 该系随地球自转,故地面上固定的点在其中坐标恒定。坐标单位为米。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Ecef(Vec3); @@ -28,22 +37,32 @@ pub struct Ecef(Vec3); /// Latitude and longitude are in radians and height is in meters. Latitude /// is geodetic, measured between the ellipsoid normal and the equatorial /// plane. +/// +/// 以纬度、经度与 WGS 84 椭球高给出的地固系位置。 +/// +/// 纬度与经度用弧度,高用米。纬度为大地纬度,在椭球法线与赤道面之间量取。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Geodetic(EllipsoidalPosition); impl Eci { /// Creates an inertial position from a vector in meters. + /// + /// 由米为单位的向量创建惯性位置。 pub const fn new(vector: Vec3) -> Self { Self(vector) } /// Returns the position in meters. + /// + /// 返回以米为单位的位置。 pub const fn vector(self) -> Vec3 { self.0 } /// Converts this inertial position to the Earth-fixed frame at the given /// Earth Rotation Angle. + /// + /// 按给定的地球自转角把该惯性位置转换到地固系。 pub fn to_ecef(self, angle: EarthRotationAngle) -> Ecef { Ecef(rotation_eci_to_ecef(angle) * self.0) } @@ -51,17 +70,23 @@ impl Eci { impl Ecef { /// Creates an Earth-fixed position from a vector in meters. + /// + /// 由米为单位的向量创建地固位置。 pub const fn new(vector: Vec3) -> Self { Self(vector) } /// Returns the position in meters. + /// + /// 返回以米为单位的位置。 pub const fn vector(self) -> Vec3 { self.0 } /// Converts this Earth-fixed position to the inertial frame at the given /// Earth Rotation Angle. + /// + /// 按给定的地球自转角把该地固位置转换到惯性系。 pub fn to_eci(self, angle: EarthRotationAngle) -> Eci { Eci(rotation_ecef_to_eci(angle) * self.0) } @@ -71,6 +96,10 @@ impl Ecef { /// /// Returns `None` when the position lies on the polar axis, where no /// longitude is defined. + /// + /// 把该地固位置转换为给定椭球之上的纬度、经度与高。 + /// + /// 当位置落在极轴上时返回 `None`,因为那里经度未定义。 pub fn to_geodetic(self, ellipsoid: Ellipsoid) -> Option { geodetic_from_cartesian(self.0, ellipsoid).map(Geodetic) } @@ -80,26 +109,38 @@ impl Geodetic { /// Creates a geodetic position from latitude, longitude, and height. /// /// Latitude and longitude are in radians and height is in meters. + /// + /// 由纬度、经度与高创建大地位置。 + /// + /// 纬度与经度用弧度,高用米。 pub const fn new(latitude: f64, longitude: f64, height: f64) -> Self { Self(EllipsoidalPosition::new(latitude, longitude, height)) } /// Returns the geodetic latitude in radians. + /// + /// 返回大地纬度,单位为弧度。 pub const fn latitude(self) -> f64 { self.0.latitude } /// Returns the longitude in radians, positive east. + /// + /// 返回经度,单位为弧度,向东为正。 pub const fn longitude(self) -> f64 { self.0.longitude } /// Returns the height above the ellipsoid in meters. + /// + /// 返回椭球高,单位为米。 pub const fn height(self) -> f64 { self.0.height } /// Returns the underlying ellipsoidal position. + /// + /// 返回底层的椭球位置。 pub const fn ellipsoidal(self) -> EllipsoidalPosition { self.0 } @@ -109,12 +150,18 @@ impl Geodetic { /// /// This is the common case. Use [`Geodetic::to_ecef_on`] for a different /// ellipsoid. + /// + /// 把该位置转换到 WGS 84 椭球的地固系。 + /// + /// 这是常见情形。需用其他椭球时请用 [`Geodetic::to_ecef_on`]。 pub fn to_ecef(self) -> Ecef { self.to_ecef_on(WGS84) } /// Converts this position to the Earth-fixed frame on the given /// ellipsoid. + /// + /// 把该位置转换到给定椭球的地固系。 pub fn to_ecef_on(self, ellipsoid: Ellipsoid) -> Ecef { Ecef(cartesian_from_geodetic(self.0, ellipsoid)) } diff --git a/crates/physics/apsis-geodesy/src/ellipsoid.rs b/crates/physics/apsis-geodesy/src/ellipsoid.rs index f40f37e..18f81df 100644 --- a/crates/physics/apsis-geodesy/src/ellipsoid.rs +++ b/crates/physics/apsis-geodesy/src/ellipsoid.rs @@ -9,6 +9,13 @@ /// there is no risk of the parameters disagreeing with each other. /// /// Lengths are in meters and angles are in radians. +/// +/// 双轴参考椭球:绕短轴旋转所得的椭圆。 +/// +/// 形状由两个数确定。本 crate 存储半长轴与逆扁率,它们是标准中 WGS 84 椭球的 +/// 两个定义参数。其余量都由二者导出,故各参数之间不会失配。 +/// +/// 长度单位为米,角度单位为弧度。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Ellipsoid { semi_major_axis: f64, @@ -21,6 +28,10 @@ impl Ellipsoid { /// /// Lengths are in meters. The inverse flattening must be positive and /// finite; a value of infinity describes a sphere. + /// + /// 由半长轴与逆扁率创建椭球。 + /// + /// 长度单位为米。逆扁率必须为正且有限;取无穷大则描述一个球。 pub const fn new(semi_major_axis: f64, inverse_flattening: f64) -> Self { Self { semi_major_axis, @@ -29,11 +40,15 @@ impl Ellipsoid { } /// Returns the semi-major axis, in meters. + /// + /// 返回半长轴,单位为米。 pub const fn semi_major_axis(self) -> f64 { self.semi_major_axis } /// Returns the inverse flattening. + /// + /// 返回逆扁率。 pub const fn inverse_flattening(self) -> f64 { self.inverse_flattening } @@ -41,11 +56,17 @@ impl Ellipsoid { /// Returns the flattening. /// /// This is zero for a sphere. + /// + /// 返回扁率。 + /// + /// 对球其值为零。 pub fn flattening(self) -> f64 { 1.0 / self.inverse_flattening } /// Returns the semi-minor axis, in meters. + /// + /// 返回半短轴,单位为米。 pub fn semi_minor_axis(self) -> f64 { self.semi_major_axis * (1.0 - self.flattening()) } @@ -54,12 +75,18 @@ impl Ellipsoid { /// `[0, 1)`. /// /// It is `2f - f^2` for flattening `f`. + /// + /// 返回第一偏心率平方,为位于 `[0, 1)` 的无量纲数。 + /// + /// 对扁率 `f`,它等于 `2f - f^2`。 pub fn eccentricity_squared(self) -> f64 { let flattening = self.flattening(); 2.0 * flattening - flattening * flattening } /// Returns the first eccentricity, a dimensionless number in `[0, 1)`. + /// + /// 返回第一偏心率,为位于 `[0, 1)` 的无量纲数。 pub fn eccentricity(self) -> f64 { self.eccentricity_squared().sqrt() } @@ -68,6 +95,10 @@ impl Ellipsoid { /// /// It is `e^2 / (1 - e^2)`, which appears in the closed form for /// converting a geodetic position to Cartesian coordinates. + /// + /// 返回第二偏心率平方,为无量纲数。 + /// + /// 它等于 `e^2 / (1 - e^2)`,出现在把大地位置转换为笛卡尔坐标的闭式解中。 pub fn second_eccentricity_squared(self) -> f64 { let squared = self.eccentricity_squared(); squared / (1.0 - squared) @@ -77,6 +108,10 @@ impl Ellipsoid { /// /// This is the radius of curvature in the meridian at the pole, equal /// to `a / sqrt(1 - e^2)`. + /// + /// 返回极半径曲率,单位为米。 + /// + /// 它是极点处子午线上的曲率半径,等于 `a / sqrt(1 - e^2)`。 pub fn polar_radius_of_curvature(self) -> f64 { self.semi_major_axis / (1.0 - self.eccentricity_squared()).sqrt() } @@ -89,6 +124,13 @@ impl Ellipsoid { /// the equator to [`Ellipsoid::polar_radius_of_curvature`] at the pole. /// /// The latitude is in radians. + /// + /// 返回给定大地纬度处卯酉圈上的曲率半径,单位为米。 + /// + /// 它是沿椭球法线从表面量到极轴的距离,由赤道处的半长轴增长到极点的 + /// [`Ellipsoid::polar_radius_of_curvature`]。 + /// + /// 纬度单位为弧度。 pub fn prime_vertical_radius(self, latitude: f64) -> f64 { let sin = latitude.sin(); self.semi_major_axis / (1.0 - self.eccentricity_squared() * sin * sin).sqrt() @@ -98,6 +140,10 @@ impl Ellipsoid { /// geodetic latitude, in meters. /// /// The latitude is in radians. + /// + /// 返回给定大地纬度处子午线上的曲率半径,单位为米。 + /// + /// 纬度单位为弧度。 pub fn meridian_radius(self, latitude: f64) -> f64 { let sin = latitude.sin(); let squared = self.eccentricity_squared(); @@ -112,6 +158,11 @@ impl Ellipsoid { /// radius vector, while the geodetic latitude is measured to the /// ellipsoid normal. The geocentric value is the smaller of the two /// away from the equator and the poles. + /// + /// 返回与给定大地纬度对应的地心纬度,单位为弧度。 + /// + /// 地心纬度自赤道面量到径向矢量,而大地纬度量到椭球法线。除赤道与极点外, + /// 地心纬度小于大地纬度。 pub fn geocentric_latitude(self, geodetic_latitude: f64) -> f64 { let squared = self.eccentricity_squared(); let tangent = (1.0 - squared) * geodetic_latitude.tan(); @@ -125,6 +176,12 @@ impl Ellipsoid { /// the inverse flattening `1 / f = 298.257 223 563`. /// /// Source: NIMA TR8350.2, third edition, 2000. +/// +/// WGS 84 参考椭球。 +/// +/// 两个定义参数为半长轴 `a = 6 378 137 m` 与逆扁率 `1 / f = 298.257 223 563`。 +/// +/// 出处:NIMA TR8350.2 第三版,2000。 pub const WGS84: Ellipsoid = Ellipsoid::new(6_378_137.0, 298.257_223_563); /// The Earth gravitational parameter for WGS 84, in cubic meters per second @@ -133,4 +190,9 @@ pub const WGS84: Ellipsoid = Ellipsoid::new(6_378_137.0, 298.257_223_563); /// This is held here rather than in `apsis-core` so that a caller working /// with the ellipsoid can use the matching gravity field. The value is /// `3.986 004 418e14 m^3/s^2`. +/// +/// WGS 84 的地球引力参数,单位为立方米每二次方秒。 +/// +/// 该值置于此处而非 `apsis-core`,以便处理椭球的调用方使用与之匹配的重力场。 +/// 其值为 `3.986 004 418e14 m^3/s^2`。 pub const WGS84_GRAVITATIONAL_PARAMETER: f64 = 3.986_004_418e14; diff --git a/crates/physics/apsis-geodesy/src/geodetic.rs b/crates/physics/apsis-geodesy/src/geodetic.rs index b0592da..28d8530 100644 --- a/crates/physics/apsis-geodesy/src/geodetic.rs +++ b/crates/physics/apsis-geodesy/src/geodetic.rs @@ -17,18 +17,32 @@ use apsis_math::Vec3; /// plane, and lies in `[-pi/2, pi/2]`. Longitude is measured east from the /// prime meridian. Height is the distance along the ellipsoid normal, taken /// as positive outside the ellipsoid. +/// +/// 大地位置:纬度、经度与椭球高。 +/// +/// 纬度与经度用弧度,高用米。纬度为大地纬度,在椭球法线与赤道面之间量取,位于 +/// `[-pi/2, pi/2]`。经度自本初子午线向东量取。高是沿椭球法线的距离,椭球 +/// 之外取正。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Geodetic { /// Geodetic latitude, in radians. + /// + /// 大地纬度,单位为弧度。 pub latitude: f64, /// Longitude, in radians, positive east. + /// + /// 经度,单位为弧度,向东为正。 pub longitude: f64, /// Height above the ellipsoid, in meters. + /// + /// 椭球高,单位为米。 pub height: f64, } impl Geodetic { /// Creates a geodetic position from latitude, longitude, and height. + /// + /// 由纬度、经度与高创建大地位置。 pub const fn new(latitude: f64, longitude: f64, height: f64) -> Self { Self { latitude, @@ -57,12 +71,30 @@ impl Geodetic { /// where `N` is the prime vertical radius of curvature. This form is /// exact everywhere, including at the poles, where the cosine factors /// vanish. + /// + /// 把该大地位置转换为给定椭球上的地固笛卡尔坐标。 + /// + /// 该系的第三轴沿椭球短轴指向北极,第一轴过赤道与本初子午线之交,第二轴构成 + /// 右手系。结果单位为米。 + /// + /// 闭式解为 + /// + /// ```text + /// N = a / sqrt(1 - e^2 sin^2(lat)) + /// x = (N + h) cos(lat) cos(lon) + /// y = (N + h) cos(lat) sin(lon) + /// z = (N (1 - e^2) + h) sin(lat) + /// ``` + /// + /// 其中 `N` 为卯酉圈曲率半径。该形式处处精确,包括余弦因子为零的极点。 pub fn to_cartesian(self, ellipsoid: Ellipsoid) -> Vec3 { cartesian_from_geodetic(self, ellipsoid) } /// Returns `true` when every field is finite and the latitude and /// longitude are inside their ranges. + /// + /// 当各字段都有限,且纬度与经度位于其范围内时返回 `true`。 pub fn is_valid(self) -> bool { let half_pi = core::f64::consts::FRAC_PI_2; self.latitude.is_finite() @@ -79,6 +111,10 @@ impl Geodetic { /// /// See [`Geodetic::to_cartesian`] for the frame definition and the closed /// form. +/// +/// 把大地位置转换为地固笛卡尔坐标。 +/// +/// 坐标系定义与闭式解见 [`Geodetic::to_cartesian`]。 pub fn cartesian_from_geodetic(geodetic: Geodetic, ellipsoid: Ellipsoid) -> Vec3 { let Geodetic { latitude, @@ -107,6 +143,13 @@ pub fn cartesian_from_geodetic(geodetic: Geodetic, ellipsoid: Ellipsoid) -> Vec3 /// /// Returns `None` when the position is not finite, or when it lies on the /// polar axis so that no longitude is defined. +/// +/// 把地固笛卡尔坐标转换为大地位置。 +/// +/// 纬度与高由 Bowring 方法求得:先以闭式解解出子午线方程,再用若干不动点迭代 +/// 精化。对从椭球下方到地球同步半径范围内的位置,该迭代收敛到远优于一微米。 +/// +/// 当位置非有限,或落在极轴上以致经度未定义时返回 `None`。 pub fn geodetic_from_cartesian(cartesian: Vec3, ellipsoid: Ellipsoid) -> Option { let Vec3 { x, y, z } = cartesian; if !cartesian.is_finite() { @@ -121,6 +164,7 @@ pub fn geodetic_from_cartesian(cartesian: Vec3, ellipsoid: Ellipsoid) -> Option< let horizontal_squared = x * x + y * y; if horizontal_squared == 0.0 { // On the polar axis the longitude is undefined. + // 在极轴上经度未定义。 return None; } let horizontal = horizontal_squared.sqrt(); @@ -128,6 +172,7 @@ pub fn geodetic_from_cartesian(cartesian: Vec3, ellipsoid: Ellipsoid) -> Option< // Bowring's initial latitude estimate, obtained by treating the point // as if it lay on a locally spherical surface. + // Bowring 的纬度初值估计,做法是把该点视为位于局部球面上。 let theta = (z * semi_major).atan2(horizontal * semi_minor); let (sin_theta, cos_theta) = theta.sin_cos(); let mut latitude = (z + second_squared * semi_minor * sin_theta * sin_theta * sin_theta) @@ -136,6 +181,8 @@ pub fn geodetic_from_cartesian(cartesian: Vec3, ellipsoid: Ellipsoid) -> Option< // Refine with fixed point steps on the ellipsoid equation. Three steps // reduce the error to the floating point noise floor for every // terrestrial altitude. + // 用椭球方程的不动点迭代精化。三步即可把误差降到浮点噪声水平,适用于 + // 任何地面高度。 for _ in 0..3 { let (sin_latitude, _) = latitude.sin_cos(); latitude = (z + squared * ellipsoid.prime_vertical_radius(latitude) * sin_latitude) @@ -145,6 +192,7 @@ pub fn geodetic_from_cartesian(cartesian: Vec3, ellipsoid: Ellipsoid) -> Option< let (sin_latitude, cos_latitude) = latitude.sin_cos(); // Guard against a latitude that has converged exactly to a pole, where // the division below would be singular. + // 防止纬度恰好收敛到极点,否则下文的除法为奇异。 if cos_latitude == 0.0 { return None; } diff --git a/crates/physics/apsis-leap/src/table.rs b/crates/physics/apsis-leap/src/table.rs index 8d385c4..0fe2e55 100644 --- a/crates/physics/apsis-leap/src/table.rs +++ b/crates/physics/apsis-leap/src/table.rs @@ -7,15 +7,28 @@ /// effect. It does not record the size of the inserted second, because in /// every announcement to date the inserted second was exactly one second /// and the new offset is one greater than the previous one. +/// +/// 一条闰秒公告。 +/// +/// 该条目记录 `TAI - UTC` 新值生效的日期。它不记录所插入闰秒的长度,因为迄今 +/// 每一次公告插入的闰秒都恰为一秒,且新偏移比前一个大一。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct LeapSecond { /// Year of the effective date, in the proleptic Gregorian calendar. + /// + /// 生效日期的年,前推格里历。 pub year: i32, /// Month of the effective date, in `1..=12`. + /// + /// 生效日期的月,取值 `1..=12`。 pub month: u32, /// Day of the effective date, in `1..=31`. + /// + /// 生效日期的日,取值 `1..=31`。 pub day: u32, /// The value of `TAI - UTC` in seconds from this date onward. + /// + /// 自该日起生效的 `TAI - UTC` 值,单位为秒。 pub tai_minus_utc: f64, } @@ -24,6 +37,10 @@ impl LeapSecond { /// /// Leap seconds take effect at 00:00:00 UTC, so the effective instant /// is the start of the given calendar day. + /// + /// 返回生效瞬时的修正儒略日(Modified Julian Date)。 + /// + /// 闰秒在 00:00:00 UTC 生效,故生效瞬时即该日历日的起点。 pub const fn effective_mjd(self) -> f64 { mjd_from_ymd(self.year, self.month, self.day) } @@ -34,6 +51,11 @@ impl LeapSecond { /// Source: IERS Bulletin C. The first entry is 1972-01-01, when the /// current scheme began with an initial offset of 10 s inherited from /// earlier adjustments. +/// +/// 闰秒表,按生效日期排序。 +/// +/// 出处:IERS Bulletin C。首条为 1972-01-01,即现行方案开始之时,其初始偏移 +/// 10 s 承自更早的调整。 pub const LEAP_SECONDS: &[LeapSecond] = &[ LeapSecond { year: 1972, @@ -209,6 +231,10 @@ pub const LEAP_SECONDS: &[LeapSecond] = &[ /// /// Queries earlier than this instant are rejected because UTC did not yet /// use integer second offsets. +/// +/// 表中首条的修正儒略日(Modified Julian Date),1972-01-01。 +/// +/// 早于该瞬时的查询会被拒绝,因为那时的 UTC 尚未采用整数秒偏移。 pub const TABLE_COVERS_FROM_MJD: f64 = mjd_from_ymd( LEAP_SECONDS[0].year, LEAP_SECONDS[0].month, @@ -219,6 +245,10 @@ pub const TABLE_COVERS_FROM_MJD: f64 = mjd_from_ymd( /// /// This is the offset that remains in force until a new leap second is /// announced. +/// +/// 自表中末条起生效的 `TAI - UTC` 值。 +/// +/// 在新的闰秒公布之前,该偏移持续有效。 pub const TAI_MINUS_UTC_AFTER_LAST_ENTRY: f64 = LEAP_SECONDS[LEAP_SECONDS.len() - 1].tai_minus_utc; /// Converts a proleptic Gregorian calendar date to a Modified Julian Date. @@ -230,6 +260,14 @@ pub const TAI_MINUS_UTC_AFTER_LAST_ENTRY: f64 = LEAP_SECONDS[LEAP_SECONDS.len() /// The computation is done in `i64` and converted once at the end, so the /// result is exact for the range of dates in which a leap second can /// plausibly occur. +/// +/// 把前推格里历日期转换为修正儒略日(Modified Julian Date)。 +/// +/// 结果是给定日期 00:00:00 的 MJD。算法采用 Fliegel-Van Flandern 整数换算, +/// 对格里历定义范围内的每个日期都精确,且无需查表。 +/// +/// 计算在 `i64` 中进行并仅在末尾换算一次,故在闰秒可能发生的日期范围内结果 +/// 精确。 pub const fn mjd_from_ymd(year: i32, month: u32, day: u32) -> f64 { let a = (14 - month as i64) / 12; let y = year as i64 + 4800 - a; @@ -237,5 +275,7 @@ pub const fn mjd_from_ymd(year: i32, month: u32, day: u32) -> f64 { let jdn = day as i64 + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045; // The Julian Day Number counts days from noon, and MJD 0 is the // midnight starting 1858-11-17, whose Julian Day Number is 2 400 001. + // 儒略日数从正午起计,而 MJD 0 是始于 1858-11-17 的午夜,其儒略日数为 + // 2 400 001。 (jdn - 2_400_001) as f64 } diff --git a/crates/physics/apsis-leap/src/time.rs b/crates/physics/apsis-leap/src/time.rs index 6092e3f..29483d4 100644 --- a/crates/physics/apsis-leap/src/time.rs +++ b/crates/physics/apsis-leap/src/time.rs @@ -9,9 +9,16 @@ use crate::table::{ /// /// MJD carries no time scale of its own, so this constant is the same /// number whether the instant is read in UTC, TAI, or any other scale. +/// +/// Unix 历元的修正儒略日(Modified Julian Date),1970-01-01T00:00:00。 +/// +/// MJD 本身不携带时间标度,故无论该瞬时按 UTC、TAI 还是其他标度读取,此常量 +/// 都是同一个数。 pub const MJD_AT_UNIX_EPOCH: f64 = 40_587.0; /// The number of seconds in a day. +/// +/// 一天中的秒数。 const SECONDS_PER_DAY: f64 = 86_400.0; /// Returns the value of `TAI - UTC` in seconds at the given UTC Modified @@ -23,6 +30,14 @@ const SECONDS_PER_DAY: f64 = 86_400.0; /// /// Returns `None` when the instant precedes 1972-01-01, before which UTC /// used fractional offsets and this table does not apply. +/// +/// 返回给定 UTC 修正儒略日(Modified Julian Date)处 `TAI - UTC` 的秒值。 +/// +/// 该偏移是阶跃函数:它在每个闰秒生效日的 00:00:00 UTC 改变,其间保持不变。 +/// 恰在生效日上的查询返回新值。 +/// +/// 当该瞬时早于 1972-01-01 时返回 `None`,因为那之前 UTC 采用小数偏移,本表 +/// 不适用。 pub fn tai_minus_utc_at_mjd(utc_mjd: f64) -> Option { if !utc_mjd.is_finite() || utc_mjd < TABLE_COVERS_FROM_MJD { return None; @@ -47,6 +62,14 @@ pub fn tai_minus_utc_at_mjd(utc_mjd: f64) -> Option { /// leap second makes some TAI instants correspond to no UTC instant. /// /// Returns `None` when the instant precedes 1972-01-01. +/// +/// 把 UTC 修正儒略日(Modified Julian Date)转换为对应的 TAI 修正儒略日。 +/// +/// 换算加上该瞬时生效的偏移。由于偏移是在 UTC 侧求值的阶跃函数,正向换算对每个 +/// UTC 瞬时都有定义;反向换算则不然,因为闰秒使某些 TAI 瞬时不对应任何 UTC +/// 瞬时。 +/// +/// 当该瞬时早于 1972-01-01 时返回 `None`。 pub fn tai_mjd_from_utc_mjd(utc_mjd: f64) -> Option { let offset = tai_minus_utc_at_mjd(utc_mjd)?; Some(utc_mjd + offset / SECONDS_PER_DAY) @@ -66,6 +89,15 @@ pub fn tai_mjd_from_utc_mjd(utc_mjd: f64) -> Option { /// inserted leap second. Such an instant has no UTC representation, because /// the UTC clock labels that interval `23:59:60`, a value that does not /// exist on the continuous Modified Julian Date scale. +/// +/// 把 TAI 修正儒略日(Modified Julian Date)转换为对应的 UTC 修正儒略日。 +/// +/// 表中每个候选偏移依次尝试;当所得 UTC 瞬时处生效的偏移与产生它的偏移相同时, +/// 该候选被接受。对给定输入至多有一个偏移满足该条件,故搜索无歧义。 +/// +/// 两种情形返回 `None`。其一是早于表中起点的瞬时,那里的 UTC 采用小数偏移,本表 +/// 未予描述。其二是严格落在所插入闰秒内部的瞬时,此类瞬时没有 UTC 表示,因为 +/// UTC 时钟把该区间标记为 `23:59:60`,而该值在连续的修正儒略日标度上并不存在。 pub fn utc_mjd_from_tai_mjd(tai_mjd: f64) -> Option { if !tai_mjd.is_finite() { return None; @@ -87,6 +119,11 @@ pub fn utc_mjd_from_tai_mjd(tai_mjd: f64) -> Option { /// the leap second table entries. It is provided so that callers do not /// have to reproduce the conversion, and because the calibration scripts in /// `validation/` need the same function on both sides of a comparison. +/// +/// 把前推格里历日期在 00:00:00 的瞬时转换为修正儒略日(Modified Julian Date)。 +/// +/// 这是用于定位闰秒表条目的日历换算之便捷再导出。提供它是为了免去调用方重复 +/// 实现该换算,也因为 `validation/` 中的标定脚本在比较的两侧都需要同一函数。 pub const fn calendar_mjd(year: i32, month: u32, day: u32) -> f64 { mjd_from_ymd(year, month, day) } diff --git a/crates/physics/apsis-time/src/calendar.rs b/crates/physics/apsis-time/src/calendar.rs index d4cc4e1..c99fc7a 100644 --- a/crates/physics/apsis-time/src/calendar.rs +++ b/crates/physics/apsis-time/src/calendar.rs @@ -18,6 +18,8 @@ use core::fmt; /// The number of seconds in a day, not counting leap seconds. +/// +/// 一天中的秒数,不计闰秒。 pub const SECONDS_PER_DAY: f64 = 86_400.0; /// A calendar date and time of day. @@ -31,24 +33,46 @@ pub const SECONDS_PER_DAY: f64 = 86_400.0; /// otherwise occupy. This crate therefore treats leap seconds only where /// they are unambiguous, in the `TAI - UTC` offset lookup, and not as /// calendar instants. +/// +/// 日历日期与日内时刻。 +/// +/// 各字段为前推格里历的字段。`second` 是位于 `[0, 60)` 的浮点数。 +/// +/// 秒数不会达到 60。闰秒在 UTC 中写作第 60 秒,但该标记在连续时间标度上并没有 +/// 唯一位置:它占据的区间正是随后午夜本应占据的区间。因此本 crate 只在无歧义的 +/// 位置处理闰秒,即 `TAI - UTC` 偏移查表中,而不把它当作日历瞬时。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Calendar { /// Year, negative for years before year 1. + /// + /// 年,公元 1 年之前为负。 pub year: i32, /// Month in `1..=12`. + /// + /// 月,取值 `1..=12`。 pub month: u32, /// Day of month in `1..=31`. + /// + /// 日,取值 `1..=31`。 pub day: u32, /// Hour in `0..=23`. + /// + /// 时,取值 `0..=23`。 pub hour: u32, /// Minute in `0..=59`. + /// + /// 分,取值 `0..=59`。 pub minute: u32, /// Second in `[0, 61)`. + /// + /// 秒,取值 `[0, 61)`。 pub second: f64, } impl Calendar { /// Creates a calendar date from its fields. + /// + /// 由各字段创建日历日期。 pub const fn new(year: i32, month: u32, day: u32, hour: u32, minute: u32, second: f64) -> Self { Self { year, @@ -61,17 +85,23 @@ impl Calendar { } /// Creates a calendar date at the start of the given day. + /// + /// 在给定日期的起点创建日历日期。 pub const fn from_ymd(year: i32, month: u32, day: u32) -> Self { Self::new(year, month, day, 0, 0, 0.0) } /// Returns the seconds elapsed since the start of the day. + /// + /// 返回自该日起点已过去的秒数。 pub fn seconds_of_day(self) -> f64 { f64::from(self.hour) * 3_600.0 + f64::from(self.minute) * 60.0 + self.second } /// Returns `true` when the fields form a date that exists in the /// proleptic Gregorian calendar. + /// + /// 当各字段构成前推格里历中存在的日期时返回 `true`。 pub fn is_valid(self) -> bool { (1..=12).contains(&self.month) && self.day >= 1 @@ -95,6 +125,8 @@ impl fmt::Display for Calendar { /// Returns `true` when `year` is a leap year in the proleptic Gregorian /// calendar. +/// +/// 当 `year` 是前推格里历中的闰年时返回 `true`。 pub const fn is_leap_year(year: i32) -> bool { (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 } @@ -102,6 +134,10 @@ pub const fn is_leap_year(year: i32) -> bool { /// Returns the number of days in the given month of the given year. /// /// Returns zero when `month` is outside `1..=12`. +/// +/// 返回给定年给定月的天数。 +/// +/// `month` 超出 `1..=12` 时返回零。 pub const fn days_in_month(year: i32, month: u32) -> u32 { match month { 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, @@ -123,10 +159,17 @@ pub const fn days_in_month(year: i32, month: u32) -> u32 { /// The result is the MJD of the start of the given day, so it is an /// integer. The time of day is ignored; use [`Calendar::seconds_of_day`] to /// account for it. +/// +/// 把日历日期(忽略日内时刻)转换为修正儒略日(Modified Julian Date)。 +/// +/// 结果是该日起点的 MJD,故为整数。日内时刻被忽略;若需计入,请用 +/// [`Calendar::seconds_of_day`]。 pub const fn mjd_from_ymd(year: i32, month: u32, day: u32) -> f64 { // Shift the year so that March is the first month, which places the // leap day at the end of the shifted year and removes a special case // from the day count. + // 平移年份使三月成为首月,从而把闰日置于平移后年份的末尾,免去天数统计中 + // 的特例。 let a = (14 - month as i64) / 12; let y = year as i64 + 4800 - a; let m = month as i64 + 12 * a - 3; @@ -138,6 +181,10 @@ pub const fn mjd_from_ymd(year: i32, month: u32, day: u32) -> f64 { /// /// The fractional part is ignored, so the result is the calendar day /// containing the instant. +/// +/// 把修正儒略日(Modified Julian Date)转换为日历日期。 +/// +/// 小数部分被忽略,故结果是包含该瞬时的日历日。 pub fn ymd_from_mjd(mjd: f64) -> (i32, u32, u32) { let jdn = mjd.floor() as i64 + 2_400_001; let a = jdn + 32_044; @@ -155,6 +202,10 @@ pub fn ymd_from_mjd(mjd: f64) -> (i32, u32, u32) { /// Keeps only the fractional part of an MJD as seconds since midnight. /// /// The result lies in `[0, 86400)` for any finite input. +/// +/// 只保留 MJD 的小数部分,表示为自午夜起的秒数。 +/// +/// 对任何有限输入,结果都位于 `[0, 86400)`。 pub fn seconds_of_day_from_mjd(mjd: f64) -> f64 { let fraction = mjd - mjd.floor(); fraction * SECONDS_PER_DAY diff --git a/crates/physics/apsis-time/src/epoch.rs b/crates/physics/apsis-time/src/epoch.rs index 73410be..9e95f5d 100644 --- a/crates/physics/apsis-time/src/epoch.rs +++ b/crates/physics/apsis-time/src/epoch.rs @@ -21,6 +21,10 @@ use core::fmt; /// /// The field is private so that the stored scale is guaranteed. Use /// [`Epoch::from_mjd`] or the named constructors to build one. +/// +/// TAI 时间尺度上的一个瞬时,以修正儒略日(Modified Julian Date)存储。 +/// +/// 字段私有以保证所存标度确定。用 [`Epoch::from_mjd`] 或具名构造函数创建。 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] pub struct Epoch { tai_mjd: f64, @@ -30,14 +34,22 @@ impl Epoch { /// The instant 2000-01-01T12:00:00 TT, which defines the J2000.0 epoch. /// /// The value is `2451545.0` on the TT scale, offset back to TAI. + /// + /// 瞬时 2000-01-01T12:00:00 TT,定义了 J2000.0 历元。 + /// + /// 该值在 TT 标度上为 `2451545.0`,并回退到 TAI。 pub const J2000: Self = Self::from_tai_mjd_const(51_544.5 - TT_MINUS_TAI / SECONDS_PER_DAY); /// Creates an epoch from a Modified Julian Date on the TAI scale. + /// + /// 由 TAI 标度上的修正儒略日创建历元。 pub const fn from_tai_mjd(tai_mjd: f64) -> Self { Self { tai_mjd } } /// Const constructor used by the associated constants. + /// + /// 供各关联常量使用的 const 构造函数。 const fn from_tai_mjd_const(tai_mjd: f64) -> Self { Self { tai_mjd } } @@ -47,6 +59,11 @@ impl Epoch { /// Returns `None` when the instant cannot be expressed on the requested /// scale. This happens for UTC before 1972, where the leap second table /// used to convert from UTC does not apply. + /// + /// 由给定标度上的修正儒略日(Modified Julian Date)创建历元。 + /// + /// 当该瞬时无法在所请求的标度上表达时返回 `None`。这发生在 1972 年之前的 + /// UTC,因为用于从 UTC 换算的闰秒表不适用。 pub fn from_mjd(mjd: f64, scale: TimeScale) -> Option { if !mjd.is_finite() { return None; @@ -68,6 +85,10 @@ impl Epoch { /// /// Returns `None` when the fields do not form a valid date, or when the /// instant cannot be expressed on the requested scale. + /// + /// 由日历日期创建历元,并按给定标度解释该日期。 + /// + /// 当各字段不构成有效日期,或该瞬时无法在所请求的标度上表达时返回 `None`。 pub fn from_calendar(calendar: Calendar, scale: TimeScale) -> Option { if !calendar.is_valid() { return None; @@ -78,6 +99,8 @@ impl Epoch { } /// Returns the instant as a Modified Julian Date on the TAI scale. + /// + /// 返回该瞬时在 TAI 标度上的修正儒略日(Modified Julian Date)。 pub const fn tai_mjd(self) -> f64 { self.tai_mjd } @@ -86,6 +109,11 @@ impl Epoch { /// /// Returns `None` when the instant precedes 1972 and the UTC scale is /// requested, because the leap second table does not cover that range. + /// + /// 返回该瞬时在给定标度上的修正儒略日(Modified Julian Date)。 + /// + /// 当该瞬时早于 1972 年且请求 UTC 标度时返回 `None`,因为闰秒表不覆盖该 + /// 区间。 pub fn mjd(self, scale: TimeScale) -> Option { match scale { TimeScale::Tai => Some(self.tai_mjd), @@ -99,6 +127,10 @@ impl Epoch { /// /// Returns `None` when the scale conversion is not available for this /// instant; see [`Epoch::mjd`]. + /// + /// 返回该瞬时在给定标度上的日历日期。 + /// + /// 当该瞬时不可进行标度换算时返回 `None`;参见 [`Epoch::mjd`]。 pub fn calendar(self, scale: TimeScale) -> Option { let mjd = self.mjd(scale)?; let (year, month, day) = ymd_from_mjd(mjd); @@ -121,6 +153,11 @@ impl Epoch { /// The result is positive when `other` is later. Because both epochs /// are stored on the uniform TAI scale, the difference is exact and /// does not depend on the scales used to create either epoch. + /// + /// 返回从 `self` 到 `other` 的带符号秒数。 + /// + /// 当 `other` 更晚时结果为正。由于两个历元都存于均匀的 TAI 标度,差值 + /// 精确,且与创建二者时所用的标度无关。 pub fn seconds_until(self, other: Self) -> f64 { (other.tai_mjd - self.tai_mjd) * SECONDS_PER_DAY } @@ -134,6 +171,13 @@ impl Epoch { /// does not have a single well defined numeric value across such an /// instant. Callers that need calendar arithmetic on the UTC scale /// should convert to a [`Calendar`], adjust it, and convert back. + /// + /// 返回平移 `seconds` 后的历元。 + /// + /// 平移量以均匀 TAI 标度上的 SI 秒计,故瞬时恰好前进相同的流逝时间。这是 + /// 唯一提供的平移方式:若按 UTC 时钟平移,当区间跨越插入的闰秒时结果会 + /// 不同,而 UTC 时钟在该瞬时前后没有单一良定义的数值。需要按 UTC 标度做 + /// 日历运算的调用方,应先转为 [`Calendar`]、调整后再转回。 pub fn add_seconds(self, seconds: f64) -> Self { Self { tai_mjd: self.tai_mjd + seconds / SECONDS_PER_DAY, @@ -141,6 +185,8 @@ impl Epoch { } /// Returns `true` when the stored Modified Julian Date is finite. + /// + /// 当所存修正儒略日(Modified Julian Date)有限时返回 `true`。 pub const fn is_finite(self) -> bool { self.tai_mjd.is_finite() } diff --git a/crates/physics/apsis-time/src/julian.rs b/crates/physics/apsis-time/src/julian.rs index 8514d20..2a81884 100644 --- a/crates/physics/apsis-time/src/julian.rs +++ b/crates/physics/apsis-time/src/julian.rs @@ -11,14 +11,20 @@ /// The number of days between the start of the Julian Date scale and the /// start of the Modified Julian Date scale. +/// +/// 儒略日标度起点与修正儒略日标度起点之间的天数。 pub const JULIAN_DATE_OFFSET: f64 = 2_400_000.5; /// Converts a Julian Date to a Modified Julian Date. +/// +/// 把儒略日(Julian Date)转换为修正儒略日(Modified Julian Date)。 pub const fn mjd_from_julian_date(julian_date: f64) -> f64 { julian_date - JULIAN_DATE_OFFSET } /// Converts a Modified Julian Date to a Julian Date. +/// +/// 把修正儒略日(Modified Julian Date)转换为儒略日(Julian Date)。 pub const fn julian_date_from_mjd(mjd: f64) -> f64 { mjd + JULIAN_DATE_OFFSET } diff --git a/crates/physics/apsis-time/src/scale.rs b/crates/physics/apsis-time/src/scale.rs index 0688fde..a5ef0e7 100644 --- a/crates/physics/apsis-time/src/scale.rs +++ b/crates/physics/apsis-time/src/scale.rs @@ -20,11 +20,19 @@ use core::fmt; /// Terrestrial Time minus International Atomic Time, in seconds. /// /// Exact by the definition of Terrestrial Time. +/// +/// 地球时(TT)减国际原子时(TAI),单位为秒。 +/// +/// 由地球时的定义精确确定。 pub const TT_MINUS_TAI: f64 = 32.184; /// International Atomic Time minus GPS time, in seconds. /// /// Exact by the definition of GPS time. +/// +/// 国际原子时(TAI)减 GPS 时,单位为秒。 +/// +/// 由 GPS 时的定义精确确定。 pub const TAI_MINUS_GPS: f64 = 19.0; /// A time scale. @@ -32,15 +40,28 @@ pub const TAI_MINUS_GPS: f64 = 19.0; /// The scale determines how a numeric instant is interpreted. It does not /// change the instant itself; converting an [`Epoch`](crate::Epoch) between /// scales only changes the number used to display it. +/// +/// 时间尺度。 +/// +/// 标度决定一个数值瞬时如何被解释,但并不改变瞬时本身;把 +/// [`Epoch`](crate::Epoch) 在标度之间换算,只改变用于显示它的数值。 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum TimeScale { /// Coordinated Universal Time, adjusted by leap seconds. + /// + /// 世界协调时,由闰秒调整。 Utc, /// International Atomic Time, a uniform scale. + /// + /// 国际原子时,均匀标度。 Tai, /// Terrestrial Time, a uniform scale defined for geocentric work. + /// + /// 地球时,为地心工作定义的均匀标度。 Tt, /// GPS time, a uniform scale maintained by the GPS constellation. + /// + /// GPS 时,由 GPS 星座维持的均匀标度。 Gps, } @@ -49,11 +70,17 @@ impl TimeScale { /// /// UTC is the only scale here that is not uniform, because leap seconds /// make some of its minutes 61 seconds long. + /// + /// 当该标度以均匀速率运行时返回 `true`。 + /// + /// 此处只有 UTC 不是均匀的,因为闰秒使其部分分钟长达 61 秒。 pub const fn is_uniform(self) -> bool { !matches!(self, Self::Utc) } /// Returns the short name of the scale, such as `TAI` or `UTC`. + /// + /// 返回该标度的简称,例如 `TAI` 或 `UTC`。 pub const fn name(self) -> &'static str { match self { Self::Utc => "UTC",