feat: 引入可切换星历模型并扩展天象观测链路

- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算
- 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询
- 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出
- 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解
- 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤
- 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持
- 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理
- 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
This commit is contained in:
2026-10-15 23:54:15 +08:00
parent 16c62a97d5
commit 044f2f4381
674 changed files with 62983 additions and 8396 deletions
File diff suppressed because it is too large Load Diff
+155
View File
@@ -0,0 +1,155 @@
// Package iau2006 IAU 2006 岁差、IAU 2000A 章动与 ICRS 到真赤道的旋转;系数表单独成包,只有调用时才链接。
package iau2006
import "math"
// Matrix 3×3 旋转矩阵,按行存放,作用于列向量 / 3×3 rotation matrix, row-major, acting on column vectors.
type Matrix [3][3]float64
const (
j2000 = 2451545.0
arcsecToRad = math.Pi / 648000
arcsecCircle = 1296000.0
obliquityJ2K = 84381.406
)
type lunisolarTerm struct {
n [5]int8
lonSin, lonSinT, lonCos float64
oblCos, oblCosT, oblSin float64
}
type planetaryTerm struct {
n [14]int8
lonSin, lonCos float64
oblSin, oblCos float64
}
func centuries(jdeTT float64) float64 { return (jdeTT - j2000) / 36525 }
// Nutation IAU 2000A 黄经章动 Δψ 与交角章动 Δε(角秒),jdeTT 为 TT 儒略日 / nutation in longitude and obliquity in arcseconds.
func Nutation(jdeTT float64) (deltaPsi, deltaEpsilon float64) {
t := centuries(jdeTT)
var fa [5]float64
for i, p := range fundamentalPolynomials {
v := (((p[4]*t+p[3])*t+p[2])*t+p[1])*t + p[0]
fa[i] = math.Mod(v, arcsecCircle) * arcsecToRad
}
var dpsi, deps float64
for _, term := range lunisolarTerms {
arg := 0.0
for i, n := range term.n {
arg += float64(n) * fa[i]
}
s, c := math.Sincos(arg)
dpsi += s*(term.lonSin+term.lonSinT*t) + c*term.lonCos
deps += c*(term.oblCos+term.oblCosT*t) + s*term.oblSin
}
var pa [14]float64
for i, p := range planetaryArguments {
pa[i] = p[0] + p[1]*t
}
pa[13] *= t
for _, term := range planetaryTerms {
arg := 0.0
for i, n := range term.n {
if n != 0 {
arg += float64(n) * pa[i]
}
}
s, c := math.Sincos(arg)
dpsi += s*term.lonSin + c*term.lonCos
deps += s*term.oblSin + c*term.oblCos
}
// 系数单位为 0.1 微角秒。
return dpsi * 1e-7, deps * 1e-7
}
// MeanObliquity IAU 2006 平黄赤交角(角秒)/ IAU 2006 mean obliquity in arcseconds.
func MeanObliquity(jdeTT float64) float64 {
t := centuries(jdeTT)
return ((((-0.0000000434*t-0.000000576)*t+0.00200340)*t-0.0001831)*t-46.836769)*t + obliquityJ2K
}
// Precession J2000 平赤道到历元平赤道的 IAU 2006 岁差矩阵 / IAU 2006 precession matrix from J2000 to the mean equator of date.
func Precession(jdeTT float64) Matrix {
t := centuries(jdeTT)
psi := ((((-0.0000000951*t+0.000132851)*t-0.00114045)*t-1.0790069)*t + 5038.481507) * t
omega := ((((0.0000003337*t-0.000000467)*t-0.00772503)*t+0.0512623)*t-0.025754)*t + obliquityJ2K
chi := ((((-0.0000000560*t+0.000170663)*t-0.00121197)*t-2.3814292)*t + 10.556403) * t
// R3(χ) R1(−ω) R3(−ψ) R1(ε0)
return multiply(rotZ(chi*arcsecToRad), multiply(rotX(-omega*arcsecToRad), multiply(rotZ(-psi*arcsecToRad), rotX(obliquityJ2K*arcsecToRad))))
}
// NutationMatrix 历元平赤道到真赤道真春分点的章动矩阵 / nutation matrix from mean to true equator of date.
func NutationMatrix(jdeTT float64) Matrix {
dpsi, deps := Nutation(jdeTT)
mean := MeanObliquity(jdeTT) * arcsecToRad
// R1(−εA−Δε) R3(−Δψ) R1(εA)
return multiply(rotX(-(mean + deps*arcsecToRad)), multiply(rotZ(-dpsi*arcsecToRad), rotX(mean)))
}
// FrameBias ICRS 到 J2000 平赤道的框架偏差矩阵 / frame bias from ICRS to the J2000 mean equator.
func FrameBias() Matrix {
const (
xi0 = -0.0166170 * arcsecToRad
eta0 = -0.0068192 * arcsecToRad
da0 = -0.01460 * arcsecToRad
)
return Matrix{
{1 - 0.5*(da0*da0+xi0*xi0), da0, -xi0},
{-da0, 1 - 0.5*(da0*da0+eta0*eta0), -eta0},
{xi0, eta0, 1 - 0.5*(eta0*eta0+xi0*xi0)},
}
}
// BiasPrecessionNutation ICRS 到真赤道真春分点的完整旋转 N·P·B / full rotation from ICRS to the true equator and equinox of date.
func BiasPrecessionNutation(jdeTT float64) Matrix {
return multiply(NutationMatrix(jdeTT), multiply(Precession(jdeTT), FrameBias()))
}
// EquationOfEquinoxes 春分点方程的主项 Δψ·cos εA(角秒),不含补充项 / equation of the equinoxes, principal term only.
func EquationOfEquinoxes(jdeTT float64) float64 {
dpsi, _ := Nutation(jdeTT)
return dpsi * math.Cos(MeanObliquity(jdeTT)*arcsecToRad)
}
// Apply 把矩阵作用于向量 / apply the matrix to a vector.
func (m Matrix) Apply(v [3]float64) [3]float64 {
var out [3]float64
for i := range out {
out[i] = m[i][0]*v[0] + m[i][1]*v[1] + m[i][2]*v[2]
}
return out
}
// Transpose 逆旋转 / inverse rotation.
func (m Matrix) Transpose() Matrix {
var out Matrix
for i := range out {
for j := range out[i] {
out[i][j] = m[j][i]
}
}
return out
}
func rotX(a float64) Matrix {
s, c := math.Sincos(a)
return Matrix{{1, 0, 0}, {0, c, s}, {0, -s, c}}
}
func rotZ(a float64) Matrix {
s, c := math.Sincos(a)
return Matrix{{c, s, 0}, {-s, c, 0}, {0, 0, 1}}
}
func multiply(a, b Matrix) Matrix {
var out Matrix
for i := range out {
for j := range out[i] {
out[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j] + a[i][2]*b[2][j]
}
}
return out
}
+61
View File
@@ -0,0 +1,61 @@
// 被测契约:章动、平黄赤交角、岁差与完整旋转矩阵和独立参考实现逐点一致。
package iau2006
import (
"math"
"testing"
)
type referenceCase struct {
jd float64
dpsi, deps float64
obliquity float64
precession Matrix
fullRotation Matrix
}
func maxDiff(a, b Matrix) float64 {
worst := 0.0
for i := range a {
for j := range a[i] {
worst = math.Max(worst, math.Abs(a[i][j]-b[i][j]))
}
}
return worst
}
func TestMatchesReference(t *testing.T) {
if d := maxDiff(FrameBias(), referenceBias); d > 1e-15 {
t.Errorf("frame bias differs by %.3g", d)
}
for _, c := range referenceCases {
dpsi, deps := Nutation(c.jd)
if math.Abs(dpsi-c.dpsi) > 1e-9 || math.Abs(deps-c.deps) > 1e-9 {
t.Errorf("jd %.1f nutation %.12f %.12f want %.12f %.12f", c.jd, dpsi, deps, c.dpsi, c.deps)
}
if d := math.Abs(MeanObliquity(c.jd) - c.obliquity); d > 1e-9 {
t.Errorf("jd %.1f obliquity differs by %.3g arcsec", c.jd, d)
}
if d := maxDiff(Precession(c.jd), c.precession); d > 1e-14 {
t.Errorf("jd %.1f precession differs by %.3g", c.jd, d)
}
// 1e-12 rad ≈ 0.2 µas
if d := maxDiff(BiasPrecessionNutation(c.jd), c.fullRotation); d > 1e-12 {
t.Errorf("jd %.1f full rotation differs by %.3g", c.jd, d)
}
}
}
func TestMatrixIsRotation(t *testing.T) {
m := BiasPrecessionNutation(2460676.5)
v := m.Transpose().Apply(m.Apply([3]float64{0.3, -0.5, 0.8}))
if math.Abs(v[0]-0.3)+math.Abs(v[1]+0.5)+math.Abs(v[2]-0.8) > 1e-14 {
t.Errorf("round trip %v", v)
}
}
func BenchmarkNutation(b *testing.B) {
for i := 0; i < b.N; i++ {
Nutation(2460676.5 + float64(i%100))
}
}
+13
View File
@@ -0,0 +1,13 @@
// Code generated by a table generator; DO NOT EDIT.
package iau2006
var referenceCases = []referenceCase{
{2451545.0, -13.93199633096007, -5.769398076465288, 84381.406, Matrix{{1.0, 0.0, 0.0}, {0.0, 0.9999999999999999, 0.0}, {0.0, 0.0, 0.9999999999999999}}, Matrix{{0.9999999977211051, 6.189983589748964e-05, 2.6948100326663827e-05}, {-6.190059052214238e-05, 0.9999999976920728, 2.800305410167079e-05}, {-2.694636687767602e-05, -2.8004722146821665e-05, 0.9999999992448143}}},
{2460676.5, 0.19738588530580575, 8.503864482236587, 84369.69650702881, Matrix{{0.9999814220288329, -0.005590636237325088, -0.002429070533135733}, {0.005590636312457951, 0.9999843722478545, -6.759157331337473e-06}, {0.0024290703602134075, -6.8210181682348825e-06, 0.9999970497809775}}, Matrix{{0.9999814159951593, -0.005591584931777626, -0.002429370755271923}, {0.0055914849254004905, 0.999984366376092, -4.7955556641352665e-05}, {0.002429600922973451, 3.4370875472927915e-05, 0.9999970479246415}}},
{2415020.0, 17.346361008950204, -2.29290423829325, 84428.2405819674, Matrix{{0.9997029457826774, 0.022351425259090313, 0.009717714871532048}, {-0.0223514242285073, 0.9997501697993981, -0.0001087245685850724}, {-0.00971771724194486, -0.00010851249613186287, 0.9999527759832679}}, Matrix{{0.9997049927128934, 0.022274226618768416, 0.00968433650024539}, {-0.022274333560550526, 0.999751891565383, -9.682908399335538e-05}, {-0.009684090527633827, -0.00011891162281681589, 0.9999531010255822}}},
{2488069.5, 3.2884077167359154, 8.564340841229999, 84334.5716917638, Matrix{{0.9997026927073499, -0.022364607649351796, -0.009713419499048586}, {0.022364608681680095, 0.9997498749687979, -0.00010852828337304388}, {0.009713417122169804, -0.00010874080893430449, 0.9999528177385406}}, Matrix{{0.999702303051242, -0.022379302275393698, -0.009719676111325164}, {0.0223789000494167, 0.9997495497804156, -0.00015015470219950598}, {0.00972060217377973, -6.740565861091262e-05, 0.9999527515587205}}},
{2378496.5, -8.527561806308212, 7.228130386554576, 84475.0608481095, Matrix{{0.9988125303022144, 0.04467517714381087, 0.019433421172230077}, {-0.04467517520562372, 0.9990014714556796, -0.00043445337347491897}, {-0.019433425627896527, -0.00043425402245517164, 0.9998110588465248}}, Matrix{{0.9988105168729325, 0.04471298454442168, 0.01944994594224395}, {-0.04471230157295813, 0.9989997942954159, -0.00047019758158968824}, {-0.01945151593255477, -0.00040001355905300756, 0.9998107213453347}}},
}
var referenceBias = [3][3]float64{{0.9999999999999942, -7.078279744199226e-08, 8.056148938997159e-08}, {7.078279744199226e-08, 0.999999999999997, 3.3060414542221477e-08}, {-8.056148938997159e-08, -3.3060414542221477e-08, 0.9999999999999962}}