Files
astro/doc/manual/ephemeris.md
T
b612 044f2f4381 feat: 引入可切换星历模型并扩展天象观测链路
- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算
- 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询
- 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出
- 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解
- 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤
- 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持
- 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理
- 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
2026-10-15 23:54:15 +08:00

9.8 KiB
Raw Blame History

显式星历实例

English | 返回 README

包级函数使用进程当前的星历模型,默认是半解析模型。要在同一个进程里并用多套模型,或接入外部星历(SPK、自研引擎),就用 astro.Context 把星历固定下来;需要直接读取几何状态(位置和速度)时,再用底层的 astro.Instance。

目录

换用另一套内置模型

astro.NewContext 创建独立上下文,模型在创建时固定:

import (
	"fmt"
	"time"

	"b612.me/astro"
)

date := time.Date(2026, 2, 17, 12, 0, 0, 0, time.FixedZone("CST", 8*3600))

semi, err := astro.NewContext(astro.ModelSemianalytic) // 默认构建使用的模型
if err != nil {
	panic(err)
}
legacy, err := astro.NewContext(astro.ModelLegacy)
if err != nil {
	panic(err)
}

ra1, dec1 := semi.Sun().ApparentRaDec(date)
ra2, dec2 := legacy.Sun().ApparentRaDec(date)
fmt.Printf("%.8f %.8f\n", ra1, dec1)
fmt.Printf("%.8f %.8f\n", ra2, dec2)
330.64554489 -11.99819781
330.64554785 -11.99820252

上下文一旦创建就固定模型,之后的 astro.SetModel 不会影响它。SetModel 改的是包级函数的默认模型,只接受两个内置模型,其他模型返回 astro.ErrUnknownModel 且不改动全局状态;自定义引擎必须显式传给 Context 或 Instance。NewContext 对未知模型返回 ErrUnknownModel,引擎个数或取值不符(内置模型带引擎、ModelCustom 不是恰好一个非 nil 引擎)返回 ErrInvalidEngine;NewModelInstance 只接受内置模型。

用 Context 求坐标、高度与升落

访问器返回的方法与同名包级函数参数、返回值一致,把 sun.Xxx 换成 ctx.Sun().Xxx 即可:

ctx, err := astro.NewContext(astro.ModelSemianalytic)
if err != nil {
	panic(err)
}
date := time.Date(2026, 2, 17, 12, 0, 0, 0, time.FixedZone("CST", 8*3600))
lon, lat := 116.4, 39.9

// 太阳:地心视赤经/赤纬,站心高度角/方位角
ra, dec := ctx.Sun().ApparentRaDec(date)
alt := ctx.Sun().Altitude(date, lon, lat)
az := ctx.Sun().Azimuth(date, lon, lat)
fmt.Printf("太阳 ra=%.6f dec=%.6f alt=%.6f az=%.6f\n", ra, dec, alt, az)

// 月球:地心坐标用 GeocentricApparentRaDec,站心量直接带经纬度
moonRa, moonDec := ctx.Moon().GeocentricApparentRaDec(date)
moonAlt := ctx.Moon().Altitude(date, lon, lat)
moonRise, err := ctx.Moon().RiseTime(date, lon, lat, 0, true)
fmt.Printf("月球 ra=%.6f dec=%.6f alt=%.6f 升起=%s err=%v\n",
	moonRa, moonDec, moonAlt, moonRise.Format("15:04:05"), err)

// 行星按包名取访问器
marsRa, marsDec := ctx.Mars().ApparentRaDec(date)
marsAlt := ctx.Mars().Altitude(date, lon, lat)
fmt.Printf("火星 ra=%.6f dec=%.6f alt=%.6f\n", marsRa, marsDec, marsAlt)
太阳 ra=330.645545 dec=-11.998198 alt=37.682918 az=171.221286
月球 ra=327.197499 dec=-14.621205 alt=34.596340 升起=06:59:57 err=<nil>
火星 ra=322.193276 dec=-16.023967 alt=34.060823

时刻为 2026-02-17 12:00 CST,观测点东经 116.4°、北纬 39.9°。RiseTime 在当天当地民用日内找不到升落时返回错误,不是零值时刻。

入口 所属包 用途
Sun() sun 地心坐标、站心高度/方位/天顶距、升落与朦影、视直径、物理星历
Moon() moon 地心与站心坐标、高度/方位、升落、月相、地心距、天平动
Mercury() … Neptune() 各行星包 视坐标、高度/方位、升落、相位与视星等、冲合和大距等事件
Eclipse() eclipse 日月食查询、地方接触时刻、食带路径
Phenomena() phenomena 黄经穿越、留、最近角距、任意高度升落、偕日可见
GeoJSON() geojson 天象地图数据

...N 方法走快速表,例如 ctx.Sun().AltitudeN(date, lon, lat, n)、ctx.Moon().TrueLoN(date, n);n 的含义见精度与性能。

按日期查询事件

事件查询也挂在模块上:

date := time.Date(2026, 3, 3, 12, 0, 0, 0, time.FixedZone("CST", 8*3600))

info, ok := ctx.Eclipse().LunarEclipseOnDate(date)
next := ctx.Eclipse().NextLunarEclipse(date)
fmt.Println(ok, info.Maximum.Format("2006-01-02 15:04 MST"), info.Type)
fmt.Println(next.Maximum.Format("2006-01-02 15:04 MST"), next.Type)
true 2026-03-03 19:33 CST total
2026-03-03 19:33 CST total

日期当天有月食时 ok 才为 true;示例日期 2026-03-03 当天有月食。筛选与搜索均使用该上下文的实例。内置模型的沙罗表按实例模型选择;自定义引擎可以描述任意轨迹,因此不推断沙罗编号,结果中 HasSaros 为 false。

接入外部星历

spk.Open 读取 JPL SPK 核,作为 astro.Engine 传给 NewContext,之后的调用方式和内置模型完全相同:

import "b612.me/astro/planet/spk"

kernel, err := spk.Open("de440s.bsp")
if err != nil {
	panic(err)
}
defer kernel.Close()

custom, err := astro.NewContext(astro.ModelCustom, kernel)
if err != nil {
	panic(err)
}

// 沿用上面的 date(2026-02-17 12:00 CST)与经纬度
ra, dec := custom.Sun().ApparentRaDec(date)
alt := custom.Sun().Altitude(date, lon, lat)
marsRa, marsDec := custom.Mars().ApparentRaDec(date)
marsAlt := custom.Mars().Altitude(date, lon, lat)
moonAlt := custom.Moon().Altitude(date, lon, lat)
fmt.Printf("太阳 ra=%.6f dec=%.6f alt=%.6f\n", ra, dec, alt)
fmt.Printf("火星 ra=%.6f dec=%.6f alt=%.6f\n", marsRa, marsDec, marsAlt)
fmt.Printf("月球 alt=%.6f\n", moonAlt)
太阳 ra=330.645545 dec=-11.998198 alt=37.682918
火星 ra=322.193276 dec=-16.023967 alt=34.060823
月球 alt=34.596325

同一时刻与内置半解析基本一致,这里月球高度差约 0.05″。

需要原始几何状态(位置和速度)时改用 Instance:

inst := astro.NewInstance(kernel)
jdTDB := calendar.Date2JD(astro.TDBFromTT(date))
pos, vel, err := inst.State(astro.BodyMoon, astro.BodyEarth, jdTDB)
start, end, ok := inst.Coverage(astro.BodyMoon)
fmt.Printf("pos=%.1f,%.1f,%.1f km\nvel=%.1f,%.1f,%.1f km/日\nerr=%v coverage=%.1f..%.1f ok=%v\n",
	pos[0], pos[1], pos[2], vel[0], vel[1], vel[2], err, start, end, ok)
pos=312305.1,-204135.4,-98208.0 km
vel=48600.2,64310.3,36543.8 km/日
err=<nil> coverage=2396752.5..2506352.5 ok=true

State 接收 TDB 历元,返回 ICRF 几何位置(km)与速度(km/日)。Coverage 给出该天体的可用历元区间,越界返回 ErrEphemerisOutOfRange。

多源叠加

astro.NewStackInstance 按先后顺序合成多个引擎,先传入的源优先。小天体核常常只给“目标相对太阳”的状态,Stack 会跨源拼出目标相对太阳系质心的状态:

de440, _ := spk.Open("de440s.bsp")
ceres, _ := spk.Open("ceres.bsp")
defer de440.Close()
defer ceres.Close()

stack := astro.NewStackInstance(de440, ceres)

// 小天体核的覆盖往往很窄,查询历元必须落在覆盖内
smallDate := time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC)
jdTT := calendar.Date2JD(astro.TTFromUTC(smallDate))
dir, dist, tau, err := stack.Apparent(astro.Body(20000001), jdTT)
fmt.Printf("%.8f,%.8f,%.8f\ndist=%.0f km tau=%.5f 日 err=%v\n", dir[0], dir[1], dir[2], dist, tau, err)
0.98097637,0.17021858,-0.09333272
dist=417020638 km tau=0.01610 日 err=<nil>

astro.Body 是 int,任意 NAIF 编号可以直接写,Ceres 是 20000001。示例这份 Ceres 核只覆盖 2026-01-01 至 01-03(stack.Coverage 返回 2461041.5..2461043.5),超出覆盖返回 ErrEphemerisOutOfRange,不会静默改用别的源。

引擎契约

  • Engine.State(target, center, jdTDB):ICRF 几何位置(km)与速度(km/日),历元为 TDB。自定义引擎必须实现它。
  • 可选的 ApparentEngine.Apparent(target, jdTT):TT 历元的地心 ICRF 单位视方向、距离(km)与光行时(日)。只实现 State 的引擎(如 SPK)走通用矢量视位置链:迭代光行时,加太阳引力偏折与相对论光行差;这需要引擎能给出地球、太阳和目标相对太阳系质心(BodySSB)的状态。
  • 引擎会被并发调用,必须只读、可重入;库不会启动 goroutine。
  • 自定义引擎的失败不回退到内置表:标量入口返回 NaN,月食核心的 Maximum 为 NaN 且各阶段标志为 false,日期查询返回未找到。
  • 标量坐标和 basic 月食入口按 TT 取历元;几何引擎目前以 TT 近似 TDB,需要严格 TDB 时直接调用 Instance.State。

限制

  • EphemerisLegacy 实例只有标量入口,没有几何引擎:Instance.State 返回 astro.ErrEphemerisNoEngine,Coverage 与 Apparent 报告不可用。需要位置和速度时用半解析模型或自定义引擎。
  • 显式实例只决定用哪份星历,不保证减小二进制体积:两张内置表都会链接进程序。
  • 半解析模型的可用区间是 TT JD -1931455.0 … 5373545.0,越界仍外推;行星现代段为 JD 2086295.0 … 2816795.0。误差见精度与性能。
  • Stack.Coverage 是各源区间的并集,中间可能有断档,也不保证中心链(Stack 为拼接状态在源之间搜索的中心传递路径)所需的每个天体都可用;以 State 在具体历元的返回值为准。
  • basic 中支持实例的入口多数带 WithInstance 后缀,带 n 参数的入口带 NWithInstance,传 nil 表示使用进程当前的全局模型。例外是影半径入口 LunarEclipseWithLunarLimb 和 18 个大气升落入口,它们把 *Instance 放在参数末尾。一般不需要直接调用这些入口。