Files
astro/basic/local_ephemeris_test.go
T
b612 2bf8478639 feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑
- 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口
- 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界
- 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名
- 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验
- 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口
- 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
2026-09-17 12:27:40 +08:00

136 lines
5.8 KiB
Go

package basic
import (
"math"
"testing"
"time"
)
func TestSolarEclipseLocalEphemerisBoundedError(t *testing.T) {
events := []struct {
name string
seed float64
}{
{"2010-01-15", JDECalc(2010, 1, 15)},
{"2014-04-29", JDECalc(2014, 4, 29)},
{"2023-04-20", JDECalc(2023, 4, 20)},
{"2031-05-21", JDECalc(2031, 5, 21)},
{"2309-06-09", JDECalc(2309, 6, 9)},
}
for _, event := range events {
t.Run(event.name, func(t *testing.T) {
center := CalcMoonSHByJDE(event.seed, 0)
ephemeris := newSolarEclipseLocalEphemeris(center)
for offset := -4.5; offset <= 4.5; offset += 0.25 {
jd := center + offset/24
approxSun, approxMoon, ok := ephemeris.equatorialAt(jd)
if !ok {
t.Fatalf("interpolator rejected in-window time %.3fh", offset)
}
exactSun, exactMoon := solarEclipseSunMoonEquatorial(jd)
for name, pair := range map[string][2][3]float64{
"sun": {solarEclipseLLRToXYZ(approxSun[0], approxSun[1], approxSun[2]), solarEclipseLLRToXYZ(exactSun[0], exactSun[1], exactSun[2])},
"moon": {solarEclipseLLRToXYZ(approxMoon[0], approxMoon[1], approxMoon[2]), solarEclipseLLRToXYZ(exactMoon[0], exactMoon[1], exactMoon[2])},
} {
if errorKM := vectorDifferenceKM(pair[0], pair[1]); errorKM > 1 {
t.Fatalf("%s interpolation error at %.3fh = %.6f km", name, offset, errorKM)
}
}
}
if _, _, ok := ephemeris.equatorialAt(center + 6.1/24); ok {
t.Fatal("interpolator should reject times outside its bounded window")
}
})
}
}
func TestOccultationLocalEphemerisBoundedError(t *testing.T) {
star := hr4799OccultationCoordinateForTest()
starTT := occultationTimeToTT(time.Date(2025, 6, 5, 20, 0, 0, 0, time.UTC))
starEphemeris := newStarOccultationLocalEphemeris(starTT, star)
for offset := -4.5; offset <= 4.5; offset += 0.25 {
jd := starTT + offset/24
got, ok := starEphemeris.stateAt(jd)
if !ok {
t.Fatalf("star interpolator rejected in-window time %.3fh", offset)
}
want := starOccultationEphemerisStateAt(jd, star)
if difference := angularSeparationDegrees(got.moonRA, got.moonDec, want.moonRA, want.moonDec); difference > 1e-5 {
t.Fatalf("moon direction interpolation error at %.3fh = %.9f deg", offset, difference)
}
if difference := angularSeparationDegrees(got.starRA, got.starDec, want.starRA, want.starDec); difference > 1e-5 {
t.Fatalf("star direction interpolation error at %.3fh = %.9f deg", offset, difference)
}
}
config, ok := planetOccultationConfigFor(OccultationSaturn)
if !ok {
t.Fatal("Saturn occultation config is unavailable")
}
planetTT := occultationTimeToTT(time.Date(2024, 8, 21, 2, 40, 0, 0, time.UTC))
planetEphemeris := newPlanetOccultationLocalEphemeris(planetTT, config)
for offset := -4.5; offset <= 4.5; offset += 0.25 {
jd := planetTT + offset/24
got, ok := planetEphemeris.stateAt(jd)
if !ok {
t.Fatalf("planet interpolator rejected in-window time %.3fh", offset)
}
want := planetOccultationEphemerisStateAt(jd, config)
if difference := angularSeparationDegrees(got.moonRA, got.moonDec, want.moonRA, want.moonDec); difference > 1e-5 {
t.Fatalf("moon direction interpolation error at %.3fh = %.9f deg", offset, difference)
}
if difference := angularSeparationDegrees(got.planetRA, got.planetDec, want.planetRA, want.planetDec); difference > 1e-5 {
t.Fatalf("planet direction interpolation error at %.3fh = %.9f deg", offset, difference)
}
if math.Abs(got.planetDistanceKM-want.planetDistanceKM) > 100 {
t.Fatalf("planet distance interpolation error at %.3fh = %.6f km", offset, math.Abs(got.planetDistanceKM-want.planetDistanceKM))
}
}
}
func TestOccultationLocalEphemerisFullWindowBoundedError(t *testing.T) {
star := hr4799OccultationCoordinateForTest()
starTT := occultationTimeToTT(time.Date(2025, 6, 5, 20, 0, 0, 0, time.UTC))
starEphemeris := newStarOccultationLocalEphemeris(starTT, star)
config, ok := planetOccultationConfigFor(OccultationSaturn)
if !ok {
t.Fatal("Saturn occultation config is unavailable")
}
planetTT := occultationTimeToTT(time.Date(2024, 8, 21, 2, 40, 0, 0, time.UTC))
planetEphemeris := newPlanetOccultationLocalEphemeris(planetTT, config)
for _, offset := range []float64{-47.5, -40, -24, 24, 40, 47.5} {
starJD := starTT + offset/24
gotStar, starOK := starEphemeris.stateAt(starJD)
wantStar := starOccultationEphemerisStateAt(starJD, star)
if !starOK {
t.Fatalf("star interpolator rejected in-window time %.3fh", offset)
}
if difference := angularSeparationDegrees(gotStar.moonRA, gotStar.moonDec, wantStar.moonRA, wantStar.moonDec); difference > 1e-5 {
t.Fatalf("star-event moon direction error at %.3fh = %.9f deg", offset, difference)
}
if difference := angularSeparationDegrees(gotStar.starRA, gotStar.starDec, wantStar.starRA, wantStar.starDec); difference > 1e-5 {
t.Fatalf("star direction error at %.3fh = %.9f deg", offset, difference)
}
planetJD := planetTT + offset/24
gotPlanet, planetOK := planetEphemeris.stateAt(planetJD)
wantPlanet := planetOccultationEphemerisStateAt(planetJD, config)
if !planetOK {
t.Fatalf("planet interpolator rejected in-window time %.3fh", offset)
}
if difference := angularSeparationDegrees(gotPlanet.moonRA, gotPlanet.moonDec, wantPlanet.moonRA, wantPlanet.moonDec); difference > 1e-5 {
t.Fatalf("planet-event moon direction error at %.3fh = %.9f deg", offset, difference)
}
if difference := angularSeparationDegrees(gotPlanet.planetRA, gotPlanet.planetDec, wantPlanet.planetRA, wantPlanet.planetDec); difference > 1e-5 {
t.Fatalf("planet direction error at %.3fh = %.9f deg", offset, difference)
}
if difference := math.Abs(gotPlanet.planetDistanceKM - wantPlanet.planetDistanceKM); difference > 100 {
t.Fatalf("planet distance error at %.3fh = %.6f km", offset, difference)
}
}
}
func vectorDifferenceKM(a, b [3]float64) float64 {
return math.Sqrt((a[0]-b[0])*(a[0]-b[0]) + (a[1]-b[1])*(a[1]-b[1]) + (a[2]-b[2])*(a[2]-b[2]))
}