- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算 - 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询 - 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出 - 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解 - 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤 - 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持 - 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理 - 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
9.8 KiB
显式星历实例
包级函数使用进程当前的星历模型,默认是半解析模型。要在同一个进程里并用多套模型,或接入外部星历(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,越界仍外推;行星现代段为 JD2086295.0 … 2816795.0。误差见精度与性能。 Stack.Coverage是各源区间的并集,中间可能有断档,也不保证中心链(Stack 为拼接状态在源之间搜索的中心传递路径)所需的每个天体都可用;以State在具体历元的返回值为准。basic中支持实例的入口多数带WithInstance后缀,带n参数的入口带NWithInstance,传nil表示使用进程当前的全局模型。例外是影半径入口LunarEclipseWithLunarLimb和 18 个大气升落入口,它们把*Instance放在参数末尾。一般不需要直接调用这些入口。