feat: 引入可切换星历模型并扩展天象观测链路
- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算 - 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询 - 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出 - 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解 - 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤 - 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持 - 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理 - 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
package ephem
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type stackTestEngine struct {
|
||||
bodies []Body
|
||||
pairs map[[2]Body][3]float64
|
||||
start float64
|
||||
end float64
|
||||
}
|
||||
|
||||
func (engine stackTestEngine) State(target, center Body, jdTDB float64) (pos, vel [3]float64, err error) {
|
||||
if value, ok := engine.pairs[[2]Body{target, center}]; ok && jdTDB >= engine.start && jdTDB <= engine.end {
|
||||
pos = value
|
||||
return pos, vel, nil
|
||||
}
|
||||
for _, body := range engine.bodies {
|
||||
if body == target || body == center {
|
||||
return pos, vel, ErrOutOfRange
|
||||
}
|
||||
}
|
||||
return pos, vel, ErrUnsupportedBody
|
||||
}
|
||||
|
||||
func (engine stackTestEngine) Coverage(target Body) (start, end float64, ok bool) {
|
||||
for _, body := range engine.bodies {
|
||||
if body == target {
|
||||
return engine.start, engine.end, true
|
||||
}
|
||||
}
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
func (engine stackTestEngine) Bodies() []Body { return append([]Body(nil), engine.bodies...) }
|
||||
|
||||
func TestStackComposesAcrossSources(t *testing.T) {
|
||||
first := stackTestEngine{
|
||||
bodies: []Body{Sun, SSB},
|
||||
pairs: map[[2]Body][3]float64{
|
||||
{Sun, SSB}: {10, 20, 30},
|
||||
},
|
||||
start: 0,
|
||||
end: 3000000,
|
||||
}
|
||||
second := stackTestEngine{
|
||||
bodies: []Body{Body(2000001), Sun},
|
||||
pairs: map[[2]Body][3]float64{
|
||||
{Body(2000001), Sun}: {1, 2, 3},
|
||||
},
|
||||
start: 0,
|
||||
end: 3000000,
|
||||
}
|
||||
stack := NewStack(first, second)
|
||||
pos, _, err := stack.State(Body(2000001), SSB, 2451545)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pos != [3]float64{11, 22, 33} {
|
||||
t.Fatalf("position = %v", pos)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackFirstSourceWinsDirectState(t *testing.T) {
|
||||
first := stackTestEngine{bodies: []Body{Mars, Earth}, pairs: map[[2]Body][3]float64{{Mars, Earth}: {1, 2, 3}}, start: 0, end: 10}
|
||||
second := stackTestEngine{bodies: []Body{Mars, Earth}, pairs: map[[2]Body][3]float64{{Mars, Earth}: {4, 5, 6}}, start: 0, end: 10}
|
||||
pos, _, err := NewStack(first, second).State(Mars, Earth, 5)
|
||||
if err != nil || pos != [3]float64{1, 2, 3} {
|
||||
t.Fatalf("state = %v, %v", pos, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackErrorPriority(t *testing.T) {
|
||||
engine := stackTestEngine{bodies: []Body{Mars}, start: 0, end: 1}
|
||||
_, _, err := NewStack(engine).State(Mars, Earth, 2)
|
||||
if !errors.Is(err, ErrOutOfRange) {
|
||||
t.Fatalf("error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackGenericApparentAcceptsArbitraryBody(t *testing.T) {
|
||||
engine := stackTestEngine{
|
||||
bodies: []Body{Earth, Sun, Body(2000001), SSB},
|
||||
pairs: map[[2]Body][3]float64{
|
||||
{Earth, SSB}: {0, 0, 0},
|
||||
{Sun, SSB}: {1000000, 0, 0},
|
||||
{Body(2000001), SSB}: {1001000, 0, 0},
|
||||
},
|
||||
start: 0,
|
||||
end: 3000000,
|
||||
}
|
||||
instance := NewInstance(NewStack(engine))
|
||||
if _, distance, _, err := instance.Apparent(Body(2000001), 2451545); err != nil || distance != 1001000 {
|
||||
t.Fatalf("apparent distance = %.3f, err = %v", distance, err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStackStateChain(b *testing.B) {
|
||||
first := stackTestEngine{bodies: []Body{Sun, SSB}, pairs: map[[2]Body][3]float64{{Sun, SSB}: {10, 20, 30}}, start: 0, end: 3000000}
|
||||
second := stackTestEngine{bodies: []Body{Body(2000001), Sun}, pairs: map[[2]Body][3]float64{{Body(2000001), Sun}: {1, 2, 3}}, start: 0, end: 3000000}
|
||||
stack := NewStack(first, second)
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, _, err := stack.State(Body(2000001), SSB, 2451545); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackChainBeyondInlineCapacity(t *testing.T) {
|
||||
bodies := []Body{SSB}
|
||||
pairs := map[[2]Body][3]float64{}
|
||||
previous := SSB
|
||||
for index := 0; index < 2*stackInlineBodies; index++ {
|
||||
body := Body(3000000 + index)
|
||||
bodies = append(bodies, body)
|
||||
pairs[[2]Body{body, previous}] = [3]float64{1, 0, 0}
|
||||
previous = body
|
||||
}
|
||||
engine := stackTestEngine{bodies: bodies, pairs: pairs, start: 0, end: 10}
|
||||
pos, _, err := NewStack(engine).State(previous, SSB, 5)
|
||||
if err != nil || pos != [3]float64{2 * stackInlineBodies, 0, 0} {
|
||||
t.Fatalf("state = %v, %v", pos, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user