feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestOccultationStationCacheRecyclingPreservesContactModel(t *testing.T) {
|
||||
context := newOccultationRiseSetContext(2451545, 10, 5, 384000, 10.5, 5.25, 1e9, 60000)
|
||||
uncached := context
|
||||
uncached.states = nil
|
||||
for pass := 0; pass < 2; pass++ {
|
||||
for index := 0; index < 4*occultationRiseSetStateCacheMaximumEntries; index++ {
|
||||
lon, lat := float64(index*7-110), float64(index-40)
|
||||
for _, total := range []bool{false, true} {
|
||||
actual, reference := context, uncached
|
||||
if total {
|
||||
actual, reference = actual.withInternalContact(), reference.withInternalContact()
|
||||
}
|
||||
if got, want := actual.stateAt(lon, lat), reference.stateAt(lon, lat); got != want {
|
||||
t.Fatalf("cached station differs: got=%+v want=%+v", got, want)
|
||||
}
|
||||
}
|
||||
if len(context.states) > occultationRiseSetStateCacheMaximumEntries {
|
||||
t.Fatal("station cache exceeded bound")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOccultationEnvelopePredictionRequiresExactCorrection(t *testing.T) {
|
||||
start := time.Date(2025, 1, 4, 0, 0, 0, 0, time.UTC)
|
||||
events, err := FindBestPlanetOccultations(start, start.Add(48*time.Hour), OccultationSaturn, OccultationSearchOptions{})
|
||||
if err != nil || len(events) != 1 {
|
||||
t.Fatalf("events=%d err=%v", len(events), err)
|
||||
}
|
||||
tt := occultationTimeToTT(events[0].Greatest)
|
||||
config, _ := planetOccultationConfigFor(OccultationSaturn)
|
||||
event := newPlanetOccultationEventCache(config)
|
||||
event.prepareLocalEphemeris(tt)
|
||||
north, _, width, ok := occultationPathLimitsAndWidthForFrame(tt, event.outerFrameAt)
|
||||
if !ok {
|
||||
t.Fatal("missing limit seed")
|
||||
}
|
||||
seed := occultationPathPointFromVector(tt, north, width, time.UTC)
|
||||
sample, ok := occultationStationCorrectEnvelopePoint(tt, seed, event.riseSetContextAt, false, time.UTC)
|
||||
if !ok {
|
||||
t.Fatal("missing exact envelope seed")
|
||||
}
|
||||
model := occultationStationEnvelopeModel{kind: occultationStationContactEnvelope}
|
||||
exact := newOccultationRiseSetEvaluationCache(event.riseSetContextAt)
|
||||
state, ok := occultationStationEnvelopeArcStateAt(sample.point, tt, exact, model)
|
||||
if !ok {
|
||||
t.Fatal("missing envelope tangent")
|
||||
}
|
||||
predictor := state.coordinates
|
||||
for index := range predictor {
|
||||
predictor[index] += 0.2 * state.tangent[index]
|
||||
}
|
||||
want, _, ok := occultationStationCorrectEnvelopeArc(predictor, state.tangent, tt, exact, model, width, time.UTC)
|
||||
if !ok {
|
||||
t.Fatal("exact correction failed")
|
||||
}
|
||||
for _, invalid := range []bool{false, true} {
|
||||
candidateCalls := 0
|
||||
cache := newOccultationRiseSetEvaluationCacheWithCandidate(event.riseSetContextAt, func(at float64) occultationRiseSetContext {
|
||||
candidateCalls++
|
||||
if invalid {
|
||||
return occultationRiseSetContext{}
|
||||
}
|
||||
return event.candidateRiseSetContextAt(at)
|
||||
})
|
||||
got, _, ok := occultationStationCorrectEnvelopeArc(predictor, state.tangent, tt, cache, model, width, time.UTC)
|
||||
if !ok || candidateCalls == 0 {
|
||||
t.Fatalf("candidate correction ok=%v calls=%d invalid=%v", ok, candidateCalls, invalid)
|
||||
}
|
||||
if distance := occultationPathDistanceKM(want.point, got.point); distance > 0.005 {
|
||||
t.Fatalf("candidate displaced exact root by %v km", distance)
|
||||
}
|
||||
evaluation := exact.evaluation(centerTimeTT(got.point.Time))
|
||||
residual, ok := model.residual(evaluation, got.point.Longitude, got.point.Latitude)
|
||||
if !ok || math.Abs(residual[0]) > model.valueTolerance() || math.Abs(residual[1]) > model.derivativeTolerance() {
|
||||
t.Fatalf("accepted inexact root %v", residual)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user