Files
astro/basic/ephemeris_instance.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

121 lines
4.7 KiB
Go

package basic
import (
"errors"
"math"
"b612.me/astro/internal/ephem"
"b612.me/astro/planet"
)
func instanceValue(instances []*Instance) *Instance {
if len(instances) == 0 {
return nil
}
return instances[0]
}
func instanceModel(inst *Instance) ephem.Model {
if inst == nil {
return ephem.Active()
}
return inst.Model()
}
// PlanetCoordinateWithInstance 指定星历实例的行星日心经纬或距离 / heliocentric planetary coordinate or distance with an explicit instance.
func PlanetCoordinateWithInstance(index, coordinate int, jde float64, inst *Instance) float64 {
return PlanetCoordinateNWithInstance(index, coordinate, jde, -1, inst)
}
// PlanetCoordinateNWithInstance 指定星历实例的行星日心经纬或距离(n 变体) / n-parameterised heliocentric planetary coordinate or distance with an explicit instance.
func PlanetCoordinateNWithInstance(index, coordinate int, jde float64, n int, inst *Instance) float64 {
if inst == nil {
return planet.WherePlanetN(index, coordinate, jde, n)
}
if inst.Model() != ephem.Custom {
return planet.WherePlanetModelN(inst.Model(), index, coordinate, jde, n)
}
value, ok := planetHeliocentricCoordinateWithInstance(index, coordinate, jde, inst)
if !ok {
return math.NaN()
}
return value
}
func boundVectorApparent(target ephem.Body, jde float64, n int, inst *Instance) (lo, bo, tau, distAU float64, ok bool) {
lo, bo, tau, distAU, ok, err := vectorApparentWithInstance(target, jde, n, inst)
if err != nil {
return math.NaN(), math.NaN(), math.NaN(), math.NaN(), true
}
return lo, bo, tau, distAU, ok
}
func instanceStateEcliptic(target ephem.Body, jde float64, inst *Instance) (lo, bo, distAU float64, ok bool) {
return instanceStateEclipticRelative(target, ephem.Earth, jde, jde, inst)
}
func instanceStateEclipticRelative(target, center ephem.Body, stateJDE, eclipticJDE float64, inst *Instance) (lo, bo, distAU float64, ok bool) {
// 几何状态捷径只给自定义引擎,内置模型必须走与包级相同的级数与视位置管线。
if inst == nil || inst.Model() != ephem.Custom {
return 0, 0, 0, false
}
state, _, err := inst.State(target, center, stateJDE)
if err != nil {
return 0, 0, 0, false
}
distanceKM := math.Sqrt(state[0]*state[0] + state[1]*state[1] + state[2]*state[2])
if distanceKM == 0 || math.IsNaN(distanceKM) || math.IsInf(distanceKM, 0) {
return 0, 0, 0, false
}
direction := [3]float64{state[0] / distanceKM, state[1] / distanceKM, state[2] / distanceKM}
lo, bo = icrfToMeanEcliptic(direction, eclipticJDE)
return lo, bo, distanceKM / astronomicalUnitKM, true
}
func icrfToMeanEcliptic(direction [3]float64, jde float64) (lo, bo float64) {
matrix := ltpPBMAT(2000 + (jde-2451545)/365.25)
var equatorial [3]float64
for axis := range equatorial {
equatorial[axis] = matrix[axis][0]*direction[0] + matrix[axis][1]*direction[1] + matrix[axis][2]*direction[2]
}
sine, cosine := math.Sincos(Obliquity1980(jde) * math.Pi / 180)
eclipticY := equatorial[1]*cosine + equatorial[2]*sine
eclipticZ := -equatorial[1]*sine + equatorial[2]*cosine
lo = math.Atan2(eclipticY, equatorial[0]) * 180 / math.Pi
bo = math.Atan2(eclipticZ, math.Hypot(equatorial[0], eclipticY)) * 180 / math.Pi
return math.Mod(lo+720, 360), bo
}
func instanceApparent(target ephem.Body, jde float64, n int, inst *Instance) (lo, bo, tau, distAU float64, ok bool, err error) {
if inst == nil || n >= 0 && inst.Model() != ephem.Custom {
return 0, 0, 0, 0, false, nil
}
return apparentState(target, jde, inst)
}
// apparentState 求视位置并校验;引擎不支持时 ok=false 且 err 为 nil,状态非法时返回 err。
func apparentState(target ephem.Body, jde float64, inst *Instance) (lo, bo, tau, distAU float64, ok bool, err error) {
direction, distanceKM, tau, err := inst.Apparent(target, jde)
if errors.Is(err, ephem.ErrUnsupportedApparent) || inst.Model() != ephem.Custom && errors.Is(err, ephem.ErrNoEngine) {
return 0, 0, 0, 0, false, nil
}
if err != nil {
return 0, 0, 0, 0, false, err
}
norm := math.Hypot(math.Hypot(direction[0], direction[1]), direction[2])
if !isFiniteFloat(distanceKM) || distanceKM <= 0 || !isFiniteFloat(norm) || norm <= 0 || !isFiniteFloat(tau) || tau < 0 {
return 0, 0, 0, 0, false, errors.New("ephem: invalid apparent state")
}
lo, bo = icrfToTrueEcliptic(direction, jde)
// 天文单位须与 vectorApparent 一致,否则实例与包级距离不逐位相等。
return lo, bo, tau, distanceKM / 149597870.7, true, nil
}
func vectorApparentWithInstance(target ephem.Body, jde float64, n int, inst *Instance) (lo, bo, tau, distAU float64, ok bool, err error) {
if inst != nil {
return instanceApparent(target, jde, n, inst)
}
lo, bo, tau, distAU, ok = vectorApparent(target, jde, n)
return lo, bo, tau, distAU, ok, nil
}