- 新增时标、ΔT 模型、质心时间与 UT1 支持 - 改进日月食、月掩、行星事件及路径边界计算 - 完善恒星三维自行与动态距离传播 - 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具 - 整理中英文手册、示例资源及回归测试
22 KiB
通用小天体轨道
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生效:这些变化率只在经典椭圆形式下参与传播。