feat: 扩展天文计算能力

- 新增日食、月食、本地可见性、中心线、半影区域、SVG 图示与沙罗周期信息
- 新增行星冲合、留、方照、物理星历、视直径、相位、亮肢角、轨道节点等计算
- 新增木星伽利略卫星位置、现象与接触事件计算
- 新增恒星星表、星座判定、自行修正与观测辅助能力
- 新增 coord、formula、orbit、sundial、lite/sun、lite/moon 等扩展包
- 完善农历年号、月相英文别名、视差角、大气质量、折射、日晷与双星计算
- 增加 NASA、JPL Horizons、IMCCE 等回归测试数据与基线测试
- 重构基础算法文件组织,补充大量公开 API 注释和语义回归测试
- 更新中文和英文 README,补充示例、精度说明、SVG 配图
This commit is contained in:
2026-05-01 22:38:44 +08:00
parent 98ff574495
commit 3ffdbe0034
365 changed files with 63589 additions and 17508 deletions
+178
View File
File diff suppressed because one or more lines are too long
+299
View File
@@ -0,0 +1,299 @@
package planet
import (
. "b612.me/astro/tools"
)
type lowMoonTerm struct {
d int
m int
mp int
f int
amp float64
}
var lowMoonITerms = []lowMoonTerm{
{d: 0, m: 0, mp: 1, f: 0, amp: 6288744},
{d: 2, m: 0, mp: -1, f: 0, amp: 1274027},
{d: 2, m: 0, mp: 0, f: 0, amp: 658314},
{d: 0, m: 0, mp: 2, f: 0, amp: 213618},
{d: 0, m: 1, mp: 0, f: 0, amp: -185116},
{d: 0, m: 0, mp: 0, f: 2, amp: -114332},
{d: 2, m: 0, mp: -2, f: 0, amp: 58793},
{d: 2, m: -1, mp: -1, f: 0, amp: 57066},
{d: 2, m: 0, mp: 1, f: 0, amp: 53322},
{d: 2, m: -1, mp: 0, f: 0, amp: 45758},
{d: 0, m: 1, mp: -1, f: 0, amp: -40923},
{d: 1, m: 0, mp: 0, f: 0, amp: -34720},
{d: 0, m: 1, mp: 1, f: 0, amp: -30383},
{d: 2, m: 0, mp: 0, f: -2, amp: 15327},
{d: 0, m: 0, mp: 1, f: 2, amp: -12528},
{d: 0, m: 0, mp: 1, f: -2, amp: 10980},
{d: 4, m: 0, mp: -1, f: 0, amp: 10675},
{d: 0, m: 0, mp: 3, f: 0, amp: 10034},
{d: 4, m: 0, mp: -2, f: 0, amp: 8548},
{d: 2, m: 1, mp: -1, f: 0, amp: -7888},
{d: 2, m: 1, mp: 0, f: 0, amp: -6766},
{d: 1, m: 0, mp: -1, f: 0, amp: -5163},
{d: 1, m: 1, mp: 0, f: 0, amp: 4987},
{d: 2, m: -1, mp: 1, f: 0, amp: 4036},
{d: 2, m: 0, mp: 2, f: 0, amp: 3994},
{d: 4, m: 0, mp: 0, f: 0, amp: 3861},
{d: 2, m: 0, mp: -3, f: 0, amp: 3665},
{d: 0, m: 1, mp: -2, f: 0, amp: -2689},
{d: 2, m: 0, mp: -1, f: 2, amp: -2602},
{d: 2, m: -1, mp: -2, f: 0, amp: 2390},
{d: 1, m: 0, mp: 1, f: 0, amp: -2348},
{d: 2, m: -2, mp: 0, f: 0, amp: 2236},
{d: 0, m: 1, mp: 2, f: 0, amp: -2120},
{d: 0, m: 2, mp: 0, f: 0, amp: -2069},
{d: 2, m: -2, mp: -1, f: 0, amp: 2048},
{d: 2, m: 0, mp: 1, f: -2, amp: -1773},
{d: 2, m: 0, mp: 0, f: 2, amp: -1595},
{d: 4, m: -1, mp: -1, f: 0, amp: 1215},
{d: 0, m: 0, mp: 2, f: 2, amp: -1110},
{d: 3, m: 0, mp: -1, f: 0, amp: -892},
{d: 2, m: 1, mp: 1, f: 0, amp: -810},
{d: 4, m: -1, mp: -2, f: 0, amp: 759},
{d: 0, m: 2, mp: -1, f: 0, amp: -713},
{d: 2, m: 2, mp: -1, f: 0, amp: -700},
{d: 2, m: 1, mp: -2, f: 0, amp: 691},
{d: 2, m: -1, mp: 0, f: -2, amp: 596},
{d: 4, m: 0, mp: 1, f: 0, amp: 549},
{d: 0, m: 0, mp: 4, f: 0, amp: 537},
{d: 4, m: -1, mp: 0, f: 0, amp: 520},
{d: 1, m: 0, mp: -2, f: 0, amp: -487},
{d: 2, m: 1, mp: 0, f: -2, amp: -399},
{d: 0, m: 0, mp: 2, f: -2, amp: -381},
{d: 1, m: 1, mp: 1, f: 0, amp: 351},
{d: 3, m: 0, mp: -2, f: 0, amp: -340},
{d: 4, m: 0, mp: -3, f: 0, amp: 330},
{d: 2, m: -1, mp: 2, f: 0, amp: 327},
{d: 0, m: 2, mp: 1, f: 0, amp: -323},
{d: 1, m: 1, mp: -1, f: 0, amp: 299},
{d: 2, m: 0, mp: 3, f: 0, amp: 294},
{d: 2, m: 0, mp: -1, f: -2, amp: 0},
}
var lowMoonRTerms = []lowMoonTerm{
{d: 0, m: 0, mp: 1, f: 0, amp: -20905355},
{d: 2, m: 0, mp: -1, f: 0, amp: -3699111},
{d: 2, m: 0, mp: 0, f: 0, amp: -2955968},
{d: 0, m: 0, mp: 2, f: 0, amp: -569925},
{d: 0, m: 1, mp: 0, f: 0, amp: 48888},
{d: 0, m: 0, mp: 0, f: 2, amp: -3149},
{d: 2, m: 0, mp: -2, f: 0, amp: 246158},
{d: 2, m: -1, mp: -1, f: 0, amp: -152138},
{d: 2, m: 0, mp: 1, f: 0, amp: -170733},
{d: 2, m: -1, mp: 0, f: 0, amp: -204586},
{d: 0, m: 1, mp: -1, f: 0, amp: -129620},
{d: 1, m: 0, mp: 0, f: 0, amp: 108743},
{d: 0, m: 1, mp: 1, f: 0, amp: 104755},
{d: 2, m: 0, mp: 0, f: -2, amp: 10321},
{d: 0, m: 0, mp: 1, f: 2, amp: 0},
{d: 0, m: 0, mp: 1, f: -2, amp: 79661},
{d: 4, m: 0, mp: -1, f: 0, amp: -34782},
{d: 0, m: 0, mp: 3, f: 0, amp: -23210},
{d: 4, m: 0, mp: -2, f: 0, amp: -21636},
{d: 2, m: 1, mp: -1, f: 0, amp: 24208},
{d: 2, m: 1, mp: 0, f: 0, amp: 30824},
{d: 1, m: 0, mp: -1, f: 0, amp: -8379},
{d: 1, m: 1, mp: 0, f: 0, amp: -16675},
{d: 2, m: -1, mp: 1, f: 0, amp: -12831},
{d: 2, m: 0, mp: 2, f: 0, amp: -10445},
{d: 4, m: 0, mp: 0, f: 0, amp: -11650},
{d: 2, m: 0, mp: -3, f: 0, amp: 14403},
{d: 0, m: 1, mp: -2, f: 0, amp: -7003},
{d: 2, m: 0, mp: -1, f: 2, amp: 0},
{d: 2, m: -1, mp: -2, f: 0, amp: 10056},
{d: 1, m: 0, mp: 1, f: 0, amp: 6322},
{d: 2, m: -2, mp: 0, f: 0, amp: -9884},
{d: 0, m: 1, mp: 2, f: 0, amp: 5751},
{d: 0, m: 2, mp: 0, f: 0, amp: 0},
{d: 2, m: -2, mp: -1, f: 0, amp: -4950},
{d: 2, m: 0, mp: 1, f: -2, amp: 4130},
{d: 2, m: 0, mp: 0, f: 2, amp: 0},
{d: 4, m: -1, mp: -1, f: 0, amp: -3958},
{d: 0, m: 0, mp: 2, f: 2, amp: 0},
{d: 3, m: 0, mp: -1, f: 0, amp: 3258},
{d: 2, m: 1, mp: 1, f: 0, amp: 2616},
{d: 4, m: -1, mp: -2, f: 0, amp: -1897},
{d: 0, m: 2, mp: -1, f: 0, amp: -2117},
{d: 2, m: 2, mp: -1, f: 0, amp: 2354},
{d: 2, m: 1, mp: -2, f: 0, amp: 0},
{d: 2, m: -1, mp: 0, f: -2, amp: 0},
{d: 4, m: 0, mp: 1, f: 0, amp: -1423},
{d: 0, m: 0, mp: 4, f: 0, amp: -1117},
{d: 4, m: -1, mp: 0, f: 0, amp: -1571},
{d: 1, m: 0, mp: -2, f: 0, amp: -1739},
{d: 2, m: 1, mp: 0, f: -2, amp: 0},
{d: 0, m: 0, mp: 2, f: -2, amp: -4421},
{d: 1, m: 1, mp: 1, f: 0, amp: 0},
{d: 3, m: 0, mp: -2, f: 0, amp: 0},
{d: 4, m: 0, mp: -3, f: 0, amp: 0},
{d: 2, m: -1, mp: 2, f: 0, amp: 0},
{d: 0, m: 2, mp: 1, f: 0, amp: 1165},
{d: 1, m: 1, mp: -1, f: 0, amp: 0},
{d: 2, m: 0, mp: 3, f: 0, amp: 0},
{d: 2, m: 0, mp: -1, f: -2, amp: 8752},
}
var lowMoonBTerms = []lowMoonTerm{
{d: 0, m: 0, mp: 0, f: 1, amp: 5128122},
{d: 0, m: 0, mp: 1, f: 1, amp: 280602},
{d: 0, m: 0, mp: 1, f: -1, amp: 277693},
{d: 2, m: 0, mp: 0, f: -1, amp: 173237},
{d: 2, m: 0, mp: -1, f: 1, amp: 55413},
{d: 2, m: 0, mp: -1, f: -1, amp: 46271},
{d: 2, m: 0, mp: 0, f: 1, amp: 32573},
{d: 0, m: 0, mp: 2, f: 1, amp: 17198},
{d: 2, m: 0, mp: 1, f: -1, amp: 9266},
{d: 0, m: 0, mp: 2, f: -1, amp: 8822},
{d: 2, m: -1, mp: 0, f: -1, amp: 8216},
{d: 2, m: 0, mp: -2, f: -1, amp: 4324},
{d: 2, m: 0, mp: 1, f: 1, amp: 4200},
{d: 2, m: 1, mp: 0, f: -1, amp: -3359},
{d: 2, m: -1, mp: -1, f: 1, amp: 2463},
{d: 2, m: -1, mp: 0, f: 1, amp: 2211},
{d: 2, m: -1, mp: -1, f: -1, amp: 2065},
{d: 0, m: 1, mp: -1, f: -1, amp: -1870},
{d: 4, m: 0, mp: -1, f: -1, amp: 1828},
{d: 0, m: 1, mp: 0, f: 1, amp: -1794},
{d: 0, m: 0, mp: 0, f: 3, amp: -1749},
{d: 0, m: 1, mp: -1, f: 1, amp: -1565},
{d: 1, m: 0, mp: 0, f: 1, amp: -1491},
{d: 0, m: 1, mp: 1, f: 1, amp: -1475},
{d: 0, m: 1, mp: 1, f: -1, amp: -1410},
{d: 0, m: 1, mp: 0, f: -1, amp: -1344},
{d: 1, m: 0, mp: 0, f: -1, amp: -1335},
{d: 0, m: 0, mp: 3, f: 1, amp: 1107},
{d: 4, m: 0, mp: 0, f: -1, amp: 1021},
{d: 4, m: 0, mp: -1, f: 1, amp: 833},
{d: 0, m: 0, mp: 1, f: -3, amp: 777},
{d: 4, m: 0, mp: -2, f: 1, amp: 671},
{d: 2, m: 0, mp: 0, f: -3, amp: 607},
{d: 2, m: 0, mp: 2, f: -1, amp: 596},
{d: 2, m: -1, mp: 1, f: -1, amp: 491},
{d: 2, m: 0, mp: -2, f: 1, amp: -451},
{d: 0, m: 0, mp: 3, f: -1, amp: 439},
{d: 2, m: 0, mp: 2, f: 1, amp: 422},
{d: 2, m: 0, mp: -3, f: -1, amp: 421},
{d: 2, m: 1, mp: -1, f: 1, amp: -366},
{d: 2, m: 1, mp: 0, f: 1, amp: -351},
{d: 4, m: 0, mp: 0, f: 1, amp: 331},
{d: 2, m: -1, mp: 1, f: 1, amp: 315},
{d: 2, m: -2, mp: 0, f: -1, amp: 302},
{d: 0, m: 0, mp: 1, f: 3, amp: -283},
{d: 2, m: 1, mp: 1, f: -1, amp: -229},
{d: 1, m: 1, mp: 0, f: -1, amp: 223},
{d: 1, m: 1, mp: 0, f: 1, amp: 223},
{d: 0, m: 1, mp: -2, f: -1, amp: -220},
{d: 2, m: 1, mp: -1, f: -1, amp: -220},
{d: 1, m: 0, mp: 1, f: 1, amp: -185},
{d: 2, m: -1, mp: -2, f: -1, amp: 181},
{d: 0, m: 1, mp: 2, f: 1, amp: -177},
{d: 4, m: 0, mp: -2, f: -1, amp: 176},
{d: 4, m: -1, mp: -1, f: -1, amp: 166},
{d: 1, m: 0, mp: 1, f: -1, amp: -164},
{d: 4, m: 0, mp: 1, f: -1, amp: 132},
{d: 1, m: 0, mp: -1, f: -1, amp: -119},
{d: 4, m: -1, mp: 0, f: -1, amp: 115},
{d: 2, m: -2, mp: 0, f: 1, amp: 107},
}
func MoonLo(JD float64) float64 { //'月球平黄经
T := (JD - 2451545) / 36525
MoonLo := 218.3164591 + 481267.88134236*T - 0.0013268*T*T + T*T*T/538841 - T*T*T*T/65194000
return MoonLo
}
func SunMoonAngle(JD float64) float64 { // '月日距角
T := (JD - 2451545) / 36525
SunMoonAngle := 297.8502042 + 445267.1115168*T - 0.00163*T*T + T*T*T/545868 - T*T*T*T/113065000
return SunMoonAngle
}
func MoonM(JD float64) float64 { // '月平近点角
T := (JD - 2451545) / 36525
MoonM := 134.9634114 + 477198.8676313*T + 0.008997*T*T + T*T*T/69699 - T*T*T*T/14712000
return MoonM
}
func MoonLonX(JD float64) float64 { // As Double '月球经度参数(到升交点的平角距离)
T := (JD - 2451545) / 36525
MoonLonX := 93.2720993 + 483202.0175273*T - 0.0034029*T*T - T*T*T/3526000 + T*T*T*T/863310000
return MoonLonX
}
func lowMoonTermValue(term lowMoonTerm, D, IsunM, IMoonM, F, E float64, trig func(float64) float64) float64 {
arg := float64(term.d)*D + float64(term.m)*IsunM + float64(term.mp)*IMoonM + float64(term.f)*F
switch Abs(term.m) {
case 1:
return trig(arg) * term.amp * E
case 2:
return trig(arg) * term.amp * E * E
default:
return trig(arg) * term.amp
}
}
func MoonI(JD float64) float64 {
T := (JD - 2451545) / 36525
D := Limit360(SunMoonAngle(JD))
IsunM := SunM(JD)
IMoonM := MoonM(JD)
F := Limit360(MoonLonX(JD))
E := 1 - 0.002516*T - 0.0000074*T*T
A1 := 119.75 + 131.849*T
A2 := Limit360(53.09 + 479264.29*T)
var MoonI float64
for _, term := range lowMoonITerms {
MoonI += lowMoonTermValue(term, D, IsunM, IMoonM, F, E, Sin)
}
MoonI = MoonI + 3958*Sin(A1) + 1962*Sin(MoonLo(JD)-F) + 318*Sin(A2)
return FR(MoonI)
}
func MoonR(JD float64) float64 {
T := (JD - 2451545) / 36525
D := SunMoonAngle(JD)
IsunM := SunM(JD)
IMoonM := Limit360(MoonM(JD))
F := Limit360(MoonLonX(JD))
E := 1 - 0.002516*T - 0.0000074*T*T
var MoonR float64
for _, term := range lowMoonRTerms {
MoonR += lowMoonTermValue(term, D, IsunM, IMoonM, F, E, Cos)
}
return MoonR
}
func MoonB(JD float64) float64 {
T := (JD - 2451545) / 36525
D := Limit360(SunMoonAngle(JD))
IsunM := Limit360(SunM(JD))
IMoonM := Limit360(MoonM(JD))
F := Limit360(MoonLonX(JD))
E := 1 - 0.002516*T - 0.0000074*T*T
A1 := Limit360(119.75 + 131.849*T)
A3 := Limit360(313.45 + 481266.484*T)
var MoonB float64
for _, term := range lowMoonBTerms {
MoonB += lowMoonTermValue(term, D, IsunM, IMoonM, F, E, Sin)
}
MoonB += -2235*Sin(MoonLo(JD)) + 382*Sin(A3) + 175*Sin(A1-F) + 175*Sin(A1+F) + 127*Sin(MoonLo(JD)-IMoonM) - 115*Sin(MoonLo(JD)+IMoonM)
return MoonB
}
func MoonTrueLo(JD float64) float64 {
return Limit360(MoonLo(JD) + (MoonI(JD) / 1000000))
}
func MoonTrueBo(JD float64) float64 {
return MoonB(JD) / 1000000
}
func MoonAway(JD float64) float64 { //'月地距离
MoonAway := 385000.56 + MoonR(JD)/1000
return MoonAway
}
+51 -222
View File
File diff suppressed because one or more lines are too long
+37 -16
View File
@@ -1,23 +1,44 @@
package planet
import (
"fmt"
"math"
"testing"
)
func Test_WherePlanet(t *testing.T) {
fmt.Println(WherePlanet(0, 0, 2452545.56))
fmt.Println(WherePlanet(0, 1, 2452545.56))
fmt.Println(WherePlanet(0, 2, 2452545.56))
fmt.Println(WherePlanet(1, 0, 2452545.56))
fmt.Println(WherePlanet(1, 1, 2452545.56))
fmt.Println(WherePlanet(1, 2, 2452545.56))
/*
184.76617673228
1.623745085328E-6
1.0021086365503
5.917793283752
-4.7404269321589
0.34835392797302
*/
func TestWherePlanetNFullMatchesDefault(t *testing.T) {
jds := []float64{
2415020.123456789,
2451545.0,
2469808.7654321,
}
for _, jd := range jds {
for xt := -1; xt < 8; xt++ {
for zn := 0; zn < 3; zn++ {
got := WherePlanet(xt, zn, jd)
gotN := WherePlanetN(xt, zn, jd, -1)
if math.Float64bits(got) != math.Float64bits(gotN) {
t.Fatalf("jd=%f xt=%d zn=%d full mismatch: got=%v gotN=%v", jd, xt, zn, got, gotN)
}
}
}
}
}
func TestPlanetViewsMatchRawCuts(t *testing.T) {
for bodyIndex, raw := range planetRawData {
view := planetViews[bodyIndex]
if math.Float64bits(view.scale) != math.Float64bits(raw[0]) {
t.Fatalf("body=%d scale mismatch", bodyIndex)
}
for zn := 0; zn < 3; zn++ {
pn := zn*6 + 1
for order := 0; order < 6; order++ {
start := int(raw[pn+order])
end := int(raw[pn+order+1])
if len(view.coords[zn].orders[order]) != end-start {
t.Fatalf("body=%d zn=%d order=%d got=%d want=%d", bodyIndex, zn, order, len(view.coords[zn].orders[order]), end-start)
}
}
}
}
}
+53
View File
@@ -0,0 +1,53 @@
package planet
import . "b612.me/astro/tools"
// SunLo 太阳几何黄经
func SunLo(jd float64) float64 {
T := (jd - 2451545) / 365250
SunLo := 280.4664567 + 360007.6982779*T + 0.03032028*T*T + T*T*T/49931 - T*T*T*T/15299 - T*T*T*T*T/1988000
return Limit360(SunLo)
}
func SunM(JD float64) float64 {
T := (JD - 2451545) / 36525
sunM := 357.5291092 + 35999.0502909*T - 0.0001559*T*T - 0.00000048*T*T*T
return Limit360(sunM)
}
// Earthe 地球偏心率
func Earthe(JD float64) float64 {
T := (JD - 2451545) / 36525
Earthe := 0.016708617 - 0.000042037*T - 0.0000001236*T*T
return Earthe
}
func EarthPI(JD float64) float64 {
T := (JD - 2451545) / 36525
return 102.93735 + 1.71953*T + 000046*T*T
}
func SunMidFun(JD float64) float64 {
T := (JD - 2451545) / 36525
M := SunM(JD)
SunMidFun := (1.9146-0.004817*T-0.000014*T*T)*Sin(M) + (0.019993-0.000101*T)*Sin(2*M) + 0.00029*Sin(3*M)
return SunMidFun
}
func SunTrueLo(JD float64) float64 {
SunTrueLo := SunLo(JD) + SunMidFun(JD)
return SunTrueLo
}
func SunApparentLo(JD float64) float64 {
T := (JD - 2451545) / 36525
SunApparentLo := SunTrueLo(JD) - 0.00569 - 0.00478*Sin(125.04-1934.136*T)
return SunApparentLo
}
func Distance(jd float64) float64 {
f := SunMidFun(jd)
m := SunM(jd)
e := Earthe(jd)
return 1.000001018 * (1 - e*e) / (1 + e*Cos(f+m))
}
+37
View File
@@ -0,0 +1,37 @@
package planet
import "fmt"
type coordSeriesView struct {
orders [6][]float64
}
type planetView struct {
scale float64
coords [3]coordSeriesView
}
var planetViews = buildPlanetViews(planetRawData)
func buildPlanetViews(rawData [][]float64) []planetView {
views := make([]planetView, len(rawData))
for bodyIndex, raw := range rawData {
if len(raw) < 20 {
panic(fmt.Sprintf("planet raw data %d too short: %d", bodyIndex, len(raw)))
}
view := planetView{scale: raw[0]}
for zn := 0; zn < 3; zn++ {
pn := zn*6 + 1
for order := 0; order < 6; order++ {
start := int(raw[pn+order])
end := int(raw[pn+order+1])
if start < 0 || end < start || end > len(raw) {
panic(fmt.Sprintf("planet raw data %d coord %d order %d invalid cut: %d..%d (len=%d)", bodyIndex, zn, order, start, end, len(raw)))
}
view.coords[zn].orders[order] = raw[start:end]
}
}
views[bodyIndex] = view
}
return views
}