Files
astro/context.go
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

238 lines
8.1 KiB
Go

package astro
import (
"sync"
"b612.me/astro/basic"
"b612.me/astro/eclipse"
"b612.me/astro/geojson"
"b612.me/astro/internal/ephem"
"b612.me/astro/jupiter"
"b612.me/astro/mars"
"b612.me/astro/mercury"
"b612.me/astro/moon"
"b612.me/astro/neptune"
"b612.me/astro/phenomena"
"b612.me/astro/saturn"
"b612.me/astro/sun"
"b612.me/astro/uranus"
"b612.me/astro/venus"
)
// Model 星历模型 / ephemeris model.
type Model = EphemerisModel
const (
// ModelLegacy VSOP87 与 ELP2000 截断级数 / truncated VSOP87 and ELP2000 series.
ModelLegacy = EphemerisLegacy
// ModelSemianalytic 半解析表模型 / semi-analytic table model.
ModelSemianalytic = EphemerisSemianalytic
// ModelCustom 自定义只读引擎 / custom read-only engine.
ModelCustom = EphemerisCustom
)
// Context 不可变星历实例与延迟创建的高层模块,须通过 NewContext 创建且不得复制;零值与 nil 没有引擎,
// 标量结果为 NaN,带输入校验的接口(如升落)返回 basic.ErrInvalidObservationInput /
// immutable ephemeris context with lazy modules; construct with NewContext and do not copy. The zero value and nil
// have no engine: scalar results are NaN and validated entry points such as rise/set return basic.ErrInvalidObservationInput.
type Context struct {
inst *ephem.Instance
// lunarLimb 日月食与月掩的默认月缘剖面,nil 为圆月面。
lunarLimb eclipse.LunarLimb
moonOnce sync.Once
moonObj *moon.Moon
sunOnce sync.Once
sunObj *sun.Sun
mercuryOnce sync.Once
mercuryObj *mercury.Mercury
venusOnce sync.Once
venusObj *venus.Venus
marsOnce sync.Once
marsObj *mars.Mars
jupiterOnce sync.Once
jupiterObj *jupiter.Jupiter
saturnOnce sync.Once
saturnObj *saturn.Saturn
uranusOnce sync.Once
uranusObj *uranus.Uranus
neptuneOnce sync.Once
neptuneObj *neptune.Neptune
eclipseOnce sync.Once
eclipseObj *eclipse.Eclipse
phenomenaOnce sync.Once
phenomenaObj *phenomena.Phenomena
geojsonOnce sync.Once
geojsonObj *geojson.Generator
}
// NewContext 创建独立模型上下文;内置模型不得带 Engine,ModelCustom 必须且只能带一个非 nil 只读 Engine,
// 违反时返回 ErrInvalidEngine,未知模型返回 ErrUnknownModel /
// create an independent context; built-in models take no Engine and ModelCustom takes exactly one non-nil Engine.
func NewContext(model Model, engines ...Engine) (*Context, error) {
switch model {
case ModelLegacy, ModelSemianalytic:
if len(engines) != 0 {
return nil, ErrInvalidEngine
}
return &Context{inst: ephem.NewModelInstance(model)}, nil
case ModelCustom:
if len(engines) != 1 || ephem.IsNilEngine(engines[0]) {
return nil, ErrInvalidEngine
}
return &Context{inst: ephem.NewInstance(engines[0])}, nil
default:
return nil, ErrUnknownModel
}
}
// NewStackContext 创建使用多源星历的独立上下文,先传入的引擎优先;含 nil 引擎时返回 ErrInvalidEngine /
// creates a multi-source context; earlier engines have priority and nil engines return ErrInvalidEngine.
func NewStackContext(engines ...Engine) (*Context, error) {
for _, engine := range engines {
if ephem.IsNilEngine(engine) {
return nil, ErrInvalidEngine
}
}
return &Context{inst: ephem.NewInstance(ephem.NewStack(engines...))}, nil
}
// Model 返回此上下文的固定模型 / the fixed model of this context.
func (context *Context) Model() Model {
return context.instance().Model()
}
// emptyInstance 零值或 nil 上下文所用的无引擎实例。
var emptyInstance = ephem.NewInstance(nil)
// nilContext nil 接收者的访问器转到这个共享零值上下文。
var nilContext = &Context{}
func (context *Context) self() *Context {
if context == nil {
return nilContext
}
return context
}
func (context *Context) instance() *ephem.Instance {
if context == nil || context.inst == nil {
return emptyInstance
}
return context.inst
}
// Moon 返回月球模块 / the lunar module.
func (context *Context) Moon() *moon.Moon {
context = context.self()
context.moonOnce.Do(func() { context.moonObj = moon.NewWithInstance(context.instance()).WithLunarLimb(context.lunarLimb) })
return context.moonObj
}
// Sun 返回太阳模块 / the solar module.
func (context *Context) Sun() *sun.Sun {
context = context.self()
context.sunOnce.Do(func() { context.sunObj = sun.NewWithInstance(context.instance()) })
return context.sunObj
}
// Mercury 返回水星模块 / the Mercury module.
func (context *Context) Mercury() *mercury.Mercury {
context = context.self()
context.mercuryOnce.Do(func() { context.mercuryObj = mercury.NewWithInstance(context.instance()) })
return context.mercuryObj
}
// Venus 返回金星模块 / the Venus module.
func (context *Context) Venus() *venus.Venus {
context = context.self()
context.venusOnce.Do(func() { context.venusObj = venus.NewWithInstance(context.instance()) })
return context.venusObj
}
// Mars 返回火星模块 / the Mars module.
func (context *Context) Mars() *mars.Mars {
context = context.self()
context.marsOnce.Do(func() { context.marsObj = mars.NewWithInstance(context.instance()) })
return context.marsObj
}
// Jupiter 返回木星模块 / the Jupiter module.
func (context *Context) Jupiter() *jupiter.Jupiter {
context = context.self()
context.jupiterOnce.Do(func() { context.jupiterObj = jupiter.NewWithInstance(context.instance()) })
return context.jupiterObj
}
// Saturn 返回土星模块 / the Saturn module.
func (context *Context) Saturn() *saturn.Saturn {
context = context.self()
context.saturnOnce.Do(func() { context.saturnObj = saturn.NewWithInstance(context.instance()) })
return context.saturnObj
}
// Uranus 返回天王星模块 / the Uranus module.
func (context *Context) Uranus() *uranus.Uranus {
context = context.self()
context.uranusOnce.Do(func() { context.uranusObj = uranus.NewWithInstance(context.instance()) })
return context.uranusObj
}
// Neptune 返回海王星模块 / the Neptune module.
func (context *Context) Neptune() *neptune.Neptune {
context = context.self()
context.neptuneOnce.Do(func() { context.neptuneObj = neptune.NewWithInstance(context.instance()) })
return context.neptuneObj
}
// Eclipse 返回日月食模块 / the eclipse module.
func (context *Context) Eclipse() *eclipse.Eclipse {
context = context.self()
context.eclipseOnce.Do(func() {
context.eclipseObj = eclipse.NewWithInstance(context.instance()).WithLunarLimb(context.lunarLimb)
})
return context.eclipseObj
}
// WithLunarLimb 共享星历实例、日月食与月掩改用指定月缘剖面的新上下文,nil 为圆月面 / context using the given lunar limb.
func (context *Context) WithLunarLimb(limb eclipse.LunarLimb) *Context {
return &Context{inst: context.instance(), lunarLimb: limb}
}
// GeoJSON 返回地理数据生成模块 / the geographic data generator.
func (context *Context) GeoJSON() *geojson.Generator {
context = context.self()
context.geojsonOnce.Do(func() { context.geojsonObj = geojson.NewWithInstance(context.instance()) })
return context.geojsonObj
}
var globalMu sync.RWMutex
// SetModel 切换包级函数的全局内置模型,非内置模型返回 ErrUnknownModel 且不改动全局状态;已有显式上下文不受影响 /
// switch the global built-in model; non-built-in models return ErrUnknownModel and leave the global state unchanged.
func SetModel(model Model) error {
if model != ModelLegacy && model != ModelSemianalytic {
return ErrUnknownModel
}
inst := ephem.NewModelInstance(model)
globalMu.Lock()
defer globalMu.Unlock()
basic.SetEphemerisModel(basic.EphemerisModel(model))
ephem.SetGlobal(inst)
return nil
}
// GetModel 返回包级函数当前使用的模型 / the active model used by package-level functions.
func GetModel() Model {
globalMu.RLock()
defer globalMu.RUnlock()
return Model(basic.GetEphemerisModel())
}
// Phenomena 返回通用天象与偕日可见模块 / the generic sky event module.
func (context *Context) Phenomena() *phenomena.Phenomena {
context = context.self()
context.phenomenaOnce.Do(func() { context.phenomenaObj = phenomena.NewWithInstance(context.instance()) })
return context.phenomenaObj
}