Files
astro/doc/manual/orbit.md
T
b612 16c62a97d5 feat: 完善时标与天象几何计算并扩展输出接口
- 新增时标、ΔT 模型、质心时间与 UT1 支持
- 改进日月食、月掩、行星事件及路径边界计算
- 完善恒星三维自行与动态距离传播
- 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具
- 整理中英文手册、示例资源及回归测试
2026-09-23 18:55:12 +08:00

22 KiB
Raw Blame History

通用小天体轨道

English | 返回 README

orbit 包用于按日心二体轨道根数传播天体位置,支持小行星、彗星、矮行星和自定义假想轨道。七大行星仍由各行星包使用内置 VSOP87 解析项计算。

orbit.Elements 支持两种常见写法:

  • 经典椭圆根数:A/E/I/Omega/W/M0
  • 近日点形式:Q/E/I/Omega/W/TpJD,适合彗星和高偏心率轨道

orbit.Elements 的参考系固定为 J2000 平黄道/平春分点。

站心与升落接口的经度东正西负、纬度北正南负、椭球高单位米;位置与站心量也能与恒星、坐标工具手册里的接口配合使用。

目录

用轨道根数计算谷神星位置

package main

import (
	"fmt"
	"log"
	"time"

	"b612.me/astro/orbit"
)

func main() {
	cst := time.FixedZone("CST", 8*3600)
	when := time.Date(2025, 11, 21, 20, 0, 0, 0, cst)
	ceres := orbit.Elements{
		EpochJD: 2461000.5, A: 2.765615651508659, E: 0.07957631994408416,
		I: 10.58788658206854, Omega: 80.24963090816965,
		W: 73.29975464616518, M0: 231.5397330043706,
	}
	pos := orbit.ApparentGeocentricEquatorial(when, ceres)
	fmt.Printf("RA=%.6f Dec=%.6f deg distance=%.6f AU\n", pos.RA, pos.Dec, pos.Distance)
	rise, err := orbit.RiseTime(when, ceres, 121.4737, 31.2304, 20, true)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(rise.Format(time.RFC3339))
}

根数使用 J2000 平黄道参考系,历元为 TT/TDB 儒略日。这里是日心二体传播;长时间跨度或近距离掠过行星时,摄动误差需要另外评估。

API 参考

轨道根数

名称 用途 单位与口径
Elements 日心二体圆锥曲线根数 EpochJD/TpJD 为 TT/TDB 儒略日;A/Q 为 AU,I/Omega/W/M0 为度,E 无量纲;ADot…MDot 为每天变化量,只作用于经典椭圆形式
MeanMotion 平均角速度 度/日;抛物线与双曲线返回 NaN;MDot 非零时直接取它
MeanAnomaly 平近点角 度([0,360));抛物线与双曲线返回 NaN
TrueAnomaly 真近点角 度([0,360));椭圆、抛物、双曲线都有解,根数非法时返回 NaN

平近点角与真近点角按同一组根数求解,MDot 可以替代默认平均角速度:

fmt.Println(orbit.MeanMotion(ceres), orbit.MeanAnomaly(when, ceres), orbit.TrueAnomaly(when, ceres))

// 抛物线与双曲线只能走近日点形式,平均角速度与平近点角没有定义。
parabolic := orbit.Elements{Q: 0.9, E: 1, I: 30, Omega: 40, W: 50, TpJD: 2461000.5}
hyperbolic := orbit.Elements{Q: 1.2, E: 1.05, I: 30, Omega: 40, W: 50, TpJD: 2461000.5}
fmt.Println(orbit.MeanMotion(parabolic), orbit.MeanAnomaly(when, parabolic))
fmt.Println(orbit.TrueAnomaly(when, parabolic), orbit.TrueAnomaly(when, hyperbolic))

完整示例同时给出椭圆根数与近日点根数两条链路:

package main

import (
	"fmt"
	"time"

	"b612.me/astro/orbit"
)

func main() {
	// 1 Ceres 的一组经典椭圆根数,参考系为 J2000 平黄道/平春分点。
	ceres := orbit.Elements{
		EpochJD: 2461000.5,
		A:       2.765615651508659,
		E:       0.07957631994408416,
		I:       10.58788658206854,
		Omega:   80.24963090816965,
		W:       73.29975464616518,
		M0:      231.5397330043706,
	}
	ceresPos := orbit.ApparentGeocentricEquatorial(
		time.Date(2025, 11, 12, 0, 0, 0, 0, time.UTC),
		ceres,
	)
	fmt.Printf("ceres ra=%.6f dec=%.6f distance=%.6f\n", ceresPos.RA, ceresPos.Dec, ceresPos.Distance)

	// 哈雷彗星示例:用近日点距离 Q 和近日点通过时刻 TpJD 描述。
	halley := orbit.Elements{
		Q:     0.5870992,
		E:     0.9671429,
		I:     162.26269,
		Omega: 58.42008,
		W:     111.33249,
		TpJD:  2446467.395,
	}
	halleyPos := orbit.ApparentGeocentricEquatorial(
		time.Date(1986, 2, 9, 0, 0, 0, 0, time.UTC),
		halley,
	)
	fmt.Printf("halley ra=%.6f dec=%.6f distance=%.6f\n", halleyPos.RA, halleyPos.Dec, halleyPos.Distance)
}

输出结果:

ceres ra=7.739532 dec=-10.625981 distance=2.164391
halley ra=312.112360 dec=-11.826451 distance=1.533936

轨道根数本身有历元,离历元越远,静态根数误差越明显。若数据源提供 ADot/EDot/IDot/OmegaDot/WDot/MDot 这类长期线性变化率,也可以填入 Elements,用于减轻中长期漂移。

位置

名称 用途 单位与口径
EclipticPosition 黄道球坐标返回值 Lon/Lat 度,Distance AU
EquatorialPosition 赤道球坐标返回值 RA/Dec 度,Distance AU
HeliocentricEclipticJ2000 日心 J2000 平黄道 几何量,不加光行时
HeliocentricEcliptic 日心历元黄道 几何量,参考系为当日平分点
GeocentricEclipticJ2000 地心 J2000 平黄道 几何量,地球与目标同取该时刻位置
GeocentricEcliptic 地心历元黄道 几何量,参考系为当日平分点
GeocentricEquatorialJ2000 地心 J2000 平赤道 几何量,黄赤交角用 J2000 值
GeocentricEquatorial 地心历元平赤道 几何量,黄赤交角取当日值
AstrometricGeocentricEquatorialJ2000 地心测算 J2000 赤道 在几何量上加光行时,可与 J2000 星表直接比对
ApparentGeocentricEcliptic 地心视黄道 光行时 + 章动,不含完整光行差
ApparentGeocentricEquatorial 地心视赤道 光行时 + 章动,不含完整光行差
ApparentTopocentricEquatorial 站心视赤道 在视赤道上再叠加站心视差修正

同一时刻沿"几何 → 光行时 → 章动 → 站心"逐层加码:

h := orbit.HeliocentricEcliptic(when, ceres)                  // 日心历元黄道,几何量
g := orbit.GeocentricEquatorialJ2000(when, ceres)             // 地心 J2000 平赤道
a := orbit.ApparentGeocentricEquatorial(when, ceres)          // 地心视赤道
t := orbit.ApparentTopocentricEquatorial(when, ceres, 121.4737, 31.2304, 20)
e := orbit.ApparentGeocentricEcliptic(when, ceres)
fmt.Printf("h=%.6f %.6f %.6f\n", h.Lon, h.Lat, h.Distance)
fmt.Printf("g=%.6f %.6f %.6f\n", g.RA, g.Dec, g.Distance)
fmt.Printf("a=%.6f %.6f t=%.6f %.6f e=%.6f\n", a.RA, a.Dec, t.RA, t.Dec, e.Lon)

...J2000 与不带后缀的历元量是两套参考系:前者固定到 J2000 平黄道/平春分点,适合与星表比对和长期存档;后者使用当日平黄道/平春分点,适合表达“当天天空”。地心量里的 Distance 在几何接口上是该时刻的瞬时距离,在 Astrometric... 上是光行时收敛后的距离,两者相差约光行时对应的位移。

几何量

名称 用途 单位与口径
SunDistance 日心距离 AU,几何量
EarthDistance 地心距离 AU,几何量
Elongation 日距角 度,地心视方向上的角距
PhaseAngle 相位角 度,0° 为全亮面朝向观测者
IlluminatedFraction 被照亮比例 无量纲,通常落在 [0,1]
Phase 被照亮比例别名 与 IlluminatedFraction 同义
ParallacticAngle 视差角(天顶方向角) 度,时角与赤纬取自同一次站心求解

orbit 也提供了常见观测几何量和轻量测光接口:

r := orbit.SunDistance(when, ceres)                         // 日心距离
delta := orbit.EarthDistance(when, ceres)                   // 地心距离
elong := orbit.Elongation(when, ceres)                      // 日距角
phase := orbit.PhaseAngle(when, ceres)                      // 相位角
k := orbit.IlluminatedFraction(when, ceres)                 // 被照亮比例
mag := orbit.AsteroidMagnitudeHG(when, ceres, 3.34, 0.12)   // H-G 视星等
q := orbit.ParallacticAngle(when, ceres, 121.4737, 31.2304, 20) // 站心视差角

fmt.Printf("r=%.6f delta=%.6f elong=%.6f phase=%.6f k=%.6f mag=%.3f q=%.6f\n",
	r, delta, elong, phase, k, mag, q)

日距角接近 180° 时相位角接近 0°,IlluminatedFraction 接近 1;这三个量都由同一组地心几何推出。

ParallacticAngle 不另求赤纬,它和 HourAngle 复用同一次站心求解,避免时角与赤纬来自两条相差约 1 ULP 的儒略日路径而引入亚纳度漂移。几何量都只依赖 date 的绝对时刻,不含观测者参数;含站心参数的只有 ParallacticAngle 一个。

升落与中天

名称 用途 单位与口径
Altitude 视高度角 度,站心视位置与观测者当地民用时刻
Zenith 天顶距 度,等于 90 - Altitude
Azimuth 视方位角 度,正北 0°、向东增加
HourAngle 站心视时角 度
CulminationTime 中天时刻 time.Time,保持输入 date 的时区
RiseTime 升起时刻 (time.Time, error),第二返回值为哨兵错误
SetTime 落下时刻 (time.Time, error),第二返回值为哨兵错误
ERR_ORBIT_NEVER_RISE 目标当日永不升起的哨兵错误 由 RiseTime 返回
ERR_ORBIT_NEVER_SET 目标当日永不落下的哨兵错误 由 SetTime 返回
fmt.Println(orbit.Zenith(when, ceres, 121.4737, 31.2304, 20))
fmt.Println(orbit.HourAngle(when, ceres, 121.4737, 31.2304, 20))
fmt.Println(orbit.CulminationTime(when, ceres, 121.4737, 31.2304, 20).Format(time.RFC3339))

day := time.Date(2025, 11, 21, 0, 0, 0, 0, site)
set, err := orbit.SetTime(day, ceres, 121.4737, 31.2304, 20, true)
if errors.Is(err, orbit.ERR_ORBIT_NEVER_SET) {
	fmt.Println("当日不落", set)
}

已有轨道根数时,也可以把它当作一个“可观测目标”来求站心观测量:

site := time.FixedZone("CST", 8*3600)
when := time.Date(2025, 11, 21, 20, 0, 0, 0, site)

alt := orbit.Altitude(when, ceres, 121.4737, 31.2304, 20) // 视高度角
az := orbit.Azimuth(when, ceres, 121.4737, 31.2304, 20)   // 视方位角
rise, _ := orbit.RiseTime(time.Date(2025, 11, 21, 0, 0, 0, 0, site), ceres, 121.4737, 31.2304, 20, true) // 升起时刻

fmt.Printf("alt=%.6f az=%.6f rise=%s\n", alt, az, rise.Format(time.RFC3339))

这些观测接口基于站心视坐标计算,适合直接拿去做小行星、彗星或自定义二体目标的升落和指向辅助。

aero 为假时判据是几何地平线,为真时把目标高度取到 -0.5667° 并叠加依椭球高、纬度算出的地平俯角。RiseTime/SetTime 的第二个返回值是真正的错误:只有当日确实没有升/落才映射成 ERR_ORBIT_NEVER_RISE / ERR_ORBIT_NEVER_SET,其余失败原样透传。

测光

名称 用途 单位与口径
AsteroidMagnitudeHG 小行星 H-G 模型视星等 absoluteMagnitude 为绝对星等 H,slopeParameter 为斜率参数 G;无单位
fmt.Printf("H-G magnitude=%.3f\n", orbit.AsteroidMagnitudeHG(when, ceres, 3.34, 0.12))
fmt.Printf("r=%.6f delta=%.6f elong=%.6f\n",
	orbit.SunDistance(when, ceres), orbit.EarthDistance(when, ceres), orbit.Elongation(when, ceres))
fmt.Printf("phase=%.6f k=%.6f k2=%.6f\n",
	orbit.PhaseAngle(when, ceres), orbit.IlluminatedFraction(when, ceres), orbit.Phase(when, ceres))

H-G 模型只用日心距、地心距和相位角,不引入目标的半径、反照率或自转;G 的取值由外部星表给出,本包不做默认值填充。

视双星

名称 用途 单位与口径
VisualBinaryElements 视双星轨道要素 PeriodYears 平太阳年,PeriastronYear 带小数的年,SemiMajorAxis 角秒,Inclination/AscendingNode/PeriastronArgument 度,Eccentricity 无量纲
VisualBinaryPosition 视双星计算结果 MeanAnomaly/EccentricAnomaly/TrueAnomaly/PositionAngle 度,Radius/Separation 角秒
VisualBinary 按时刻求视双星位置 先把时刻换算为 UTC 小数年,再套经典视轨道公式
VisualBinaryByYear 按小数年求视双星位置 直接给小数年,跳过时刻换算
gammaVir := orbit.VisualBinaryElements{
	PeriodYears: 171.37, PeriastronYear: 1836.433, Eccentricity: 0.8808,
	SemiMajorAxis: 3.746, Inclination: 146.05, AscendingNode: 31.78, PeriastronArgument: 252.88,
}
vb := orbit.VisualBinaryByYear(2026.0, gammaVir)
fmt.Printf("theta=%.6f rho=%.6f M=%.6f\n", vb.PositionAngle, vb.Separation, vb.MeanAnomaly)

orbit 里还带了一个视双星求解器,直接按《天文算法》第 55 章的经典表观轨道公式输出位置角和角距:

gammaVir := orbit.VisualBinaryElements{
	PeriodYears:        171.37,
	PeriastronYear:     1836.433,
	Eccentricity:       0.8808,
	SemiMajorAxis:      3.746,
	Inclination:        146.05,
	AscendingNode:      31.78,
	PeriastronArgument: 252.88,
}
vb := orbit.VisualBinary(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), gammaVir)
fmt.Printf("theta=%.6f rho=%.6f\n", vb.PositionAngle, vb.Separation) // 位置角与角距

位置角按北为 0°、东为 90° 度量,角距离与径矢 Radius 的单位都是角秒。

常用场景

按位置层次取小行星坐标

helio := orbit.HeliocentricEcliptic(when, ceres)
geo := orbit.GeocentricEclipticJ2000(when, ceres)
ast := orbit.AstrometricGeocentricEquatorialJ2000(when, ceres)
app := orbit.ApparentGeocentricEquatorial(when, ceres)
fmt.Println(helio.Lon, helio.Lat, helio.Distance)
fmt.Println(geo.Lon, geo.Lat, geo.Distance)
fmt.Println(ast.RA, ast.Dec)
fmt.Println(app.RA, app.Dec, app.Distance)
19.251489 -9.315340 2.912174
2.147652 -12.026350 2.262445
6.795211 -10.172420
7.125008 -10.028726 2.262489

四个层次含义不同:Heliocentric* 是相对太阳的位置(第一个数就是日心距),Geocentric*J2000 是 J2000 口径的地心位置,Astrometric*J2000 去掉光行时、适合与星表对表,Apparent* 是当日视位置、观测与出图用它。需要站心视位置用 ApparentTopocentricEquatorial(when, ceres, lon, lat, height)。

今晚会不会升起、现在多高

fmt.Println(orbit.RiseTime(when, ceres, 121.4737, 31.2304, 20, true))
fmt.Println(orbit.CulminationTime(when, ceres, 121.4737, 31.2304, 20))
fmt.Println(orbit.Altitude(when, ceres, 121.4737, 31.2304, 20),
	orbit.Azimuth(when, ceres, 121.4737, 31.2304, 20))
2025-11-21 14:41:48.913 CST <nil>
2025-11-21 20:19:34 CST
48.472628 172.699168
  • aero = true 用蒙气差与视半径修正后的地平;height 是椭球高(米);经度东正西负。
  • 环极或极区目标没有升落:RiseTime/SetTime 返回 orbit.ERR_ORBIT_NEVER_RISE / ERR_ORBIT_NEVER_SET 哨兵错误,用 errors.Is 判定。

距日距地、相位角与 H-G 视星等

fmt.Println(orbit.SunDistance(when, ceres), orbit.EarthDistance(when, ceres))
fmt.Println(orbit.Elongation(when, ceres), orbit.PhaseAngle(when, ceres))
fmt.Println(orbit.IlluminatedFraction(when, ceres), orbit.AsteroidMagnitudeHG(when, ceres, 3.34, 0.12))
2.912174 2.262445
122.264630 16.670089
0.978986 8.360
  • PhaseAngle 是太阳-天体-地球夹角(度),IlluminatedFraction 是照明比例;Phase 只是 IlluminatedFraction 的别名,别把它当成相位角。
  • H-G 视星等要传绝对星等 H 与斜率参数 G(示例用谷神星的 3.34 与 0.12)。

轨道类型与位置层次

parabolic := orbit.Elements{Q: 0.9, E: 1, I: 30, Omega: 40, W: 50, TpJD: 2461000.5}
hyperbolic := orbit.Elements{Q: 1.2, E: 1.05, I: 30, Omega: 40, W: 50, TpJD: 2461000.5}
fmt.Println(orbit.MeanMotion(parabolic), orbit.MeanAnomaly(when, parabolic))
fmt.Println(orbit.TrueAnomaly(when, parabolic), orbit.TrueAnomaly(when, hyperbolic))
fmt.Println(orbit.MeanMotion(ceres))
NaN NaN
0.817533 0.537610
0.21429712142765137
  • 抛物线与双曲线只能走近日点形式(Q + TpJD):平均角速度与平近点角没有定义、返回 NaN,真近点角三种轨道都有解。
  • 与外部星历对表时先统一位置层次与参考系(Elements 固定为 J2000 平黄道/平春分点);ADot…WDot 只作用于经典椭圆形式,MDot 非零时可直接替代默认平均角速度。

参数与返回值约定

位置层次

层次 代表接口 包含的改正
日心几何 HeliocentricEcliptic / HeliocentricEclipticJ2000 无
地心几何 GeocentricEcliptic / GeocentricEclipticJ2000 / GeocentricEquatorial / GeocentricEquatorialJ2000 减去地球日心位置
地心测算 AstrometricGeocentricEquatorialJ2000 光行时
地心视 ApparentGeocentricEcliptic / ApparentGeocentricEquatorial 光行时 + 章动
站心视 ApparentTopocentricEquatorial 及全部升落接口 光行时 + 章动 + 站心视差

单位与坐标口径

  • 角度一律用度,距离用 AU,站心与升落接口的椭球高用米,时间用 time.Time。
  • Elements 的参考系是 J2000 平黄道/平春分点;...J2000 结尾的接口保持该参考系,其余 HeliocentricEcliptic/GeocentricEcliptic/GeocentricEquatorial 是历元(of date)量,换参考系时不要混用。
  • 位置分三层:Heliocentric*/Geocentric* 是几何量,AstrometricGeocentricEquatorialJ2000 在几何量上加光行时(按距离迭代求解,上限 8 次、1e-12 天收敛),Apparent* 在光行时之上再加章动。仓库的行星口径就是“光行时 + 章动、不含完整外部光行差模型”,所以 Apparent* 与行星包同级,不应把它当成全项视位置。
  • MeanMotion/MeanAnomaly/TrueAnomaly 输出度;MeanMotion 与 MeanAnomaly 对抛物线和双曲线没有定义。

时标与输入格式

  • EpochJD 与 TpJD 都是 TT/TDB 儒略日;坐标、几何量与测光接口把 date 当绝对时刻处理(date.UTC() 后经 UTC2TT 换成 TT/TDB)。UTC2TT 在 1972-01-01 之前把民用时刻按 UT1 处理,窗口内使用内置闰秒表,闰秒表可用 astro.SetTTMinusUTC 覆盖。
  • 瞬时站心量将 date 的当地字段与 date.Zone() 偏移一起换回绝对时刻。同一时刻用 UTC 或当地时区表示,所得位置相同。升落搜索还按当地日期选事件,因此应选择观测日历所用的时区。
  • CulminationTime、RiseTime、SetTime 的结果保持输入 date 的时区,是民用时刻;三者内部在 date.Hour() > 12 时先回退 12 小时,以保持搜索锚点在所选日期内。
  • 公开 API 的含 time.Time 输出一律是民用时刻(UTC 标签);UT1 与 TT 只出现在内部换算里,需要显式换算时用根包的 astro.UT1FromUTC / astro.TTFromUTC。

零值与越界

  • Elements 零值不是合法轨道。经典椭圆形式要求 A 有限且为正、E 落在 [0,1)、EpochJD 与 M0 有限;E >= 1 的抛物线与双曲线只能走近日点形式,此时要求 Q 有限且为正、TpJD 有限、E >= 0,三个角度 I/Omega/W 都必须有限。
  • Q > 0 且 TpJD 有限时优先按近日点形式解释,A/M0/EpochJD 被忽略;此时 ADot/EDot/IDot/OmegaDot/WDot 都不生效,只有 MDot 非零时会被当作平均角速度使用。
  • 根数非法时 MeanMotion、MeanAnomaly 返回 NaN,位置接口返回三个 NaN;AsteroidMagnitudeHG 在输入非有限或日心距、地心距、相位角非正时返回 NaN,H-G 相位混合项为零时返回 +Inf。
  • RiseTime 与 SetTime 在给定当地日内找不到升/落时返回哨兵错误 ERR_ORBIT_NEVER_RISE / ERR_ORBIT_NEVER_SET,其它失败原样透传;成功时第二个返回值为 nil。
  • VisualBinary 与 VisualBinaryByYear 在 PeriodYears <= 0、SemiMajorAxis <= 0、Eccentricity 不落在 [0,1) 或任一要素非有限时,把 VisualBinaryPosition 的全部数值字段填成 NaN。

精度与适用范围

  • orbit 是二体圆锥曲线传播:只含所给根数,不含行星摄动、非引力项与相对论改正;离历元越远,静态根数的误差越大,ADot…WDot 只能缓解线性漂移,不能替代重新拟合或数值积分。
  • Apparent* 与站心量的差别只在几何与章动,不含大气折射;升落接口的 aero=true 才把地平折射算进判据(目标高度取 -0.5667°,再叠加依椭球高与纬度算出的地平俯角)。
  • RiseTime/SetTime 内部用 round(observerLon/15) 的标称时区迭代升落几何,因此观测点经度最好落在时区中心附近;只调用一次求根,极区或拱极目标的边界情况以哨兵错误为准。
  • 视双星求解器用的是《天文算法》第 55 章的经典视轨道公式,Eccentricity >= 1 时不适用;它只做几何投影,不含质量、光度或摄动信息。

常见误用

  • 把 HeliocentricEcliptic 的返回值当作地心坐标:日心量的原点是太阳,地心量已经减掉了地球的日心位置。
  • 把 Apparent* 当作含大气折射的视位置:折射只出现在升落判据与观测类接口里,坐标接口不含折射。
  • 用零值 Elements 探路:MeanMotion 与位置接口会安静地返回 NaN,不会报错。
  • 在近日点形式里期待 ADot…WDot 生效:这些变化率只在经典椭圆形式下参与传播。

相关手册

需要把这里的黄道/赤道坐标接到恒星表或站心量上时,参见恒星与坐标工具。