feat: 引入可切换星历模型并扩展天象观测链路
- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算 - 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询 - 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出 - 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解 - 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤 - 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持 - 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理 - 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"b612.me/astro/basic"
|
||||
"b612.me/astro/internal/ephem"
|
||||
)
|
||||
|
||||
// 默认只校验发布表的新鲜度与金标准;重新生成必须同时给出 -saros-generate 与环境变量。
|
||||
@@ -41,7 +42,7 @@ type sarosTableGolden struct {
|
||||
span sarosSpan
|
||||
}
|
||||
|
||||
var sarosTableGoldens = []sarosTableGolden{
|
||||
var sarosTableLegacyGoldens = []sarosTableGolden{
|
||||
{name: "solar/head", span: sarosSpan{Series: -47, First: -79891, Last: -61605, Member: 1, Count: 83}},
|
||||
{name: "solar/202", span: sarosSpan{Series: 202, First: 15941, Last: 31551, Member: 1, Count: 71}},
|
||||
{name: "solar/tail", span: sarosSpan{Series: 293, First: 48742, Last: 63906, Member: 1, Count: 69}},
|
||||
@@ -51,6 +52,21 @@ var sarosTableGoldens = []sarosTableGolden{
|
||||
{name: "lunar/tail", span: sarosSpan{Series: 287, First: 49211, Last: 64821, Member: 1, Count: 71}},
|
||||
}
|
||||
|
||||
func sarosTableGoldens() []sarosTableGolden {
|
||||
if ephem.Active() != ephem.Semianalytic {
|
||||
return sarosTableLegacyGoldens
|
||||
}
|
||||
return []sarosTableGolden{
|
||||
{name: "solar/head", span: sarosSpan{Series: -47, First: -80114, Last: -61605, Member: 1, Count: 84}},
|
||||
{name: "solar/202", span: sarosSpan{Series: 202, First: 16164, Last: 31551, Member: 1, Count: 70}},
|
||||
{name: "solar/tail", span: sarosSpan{Series: 293, First: 48742, Last: 63906, Member: 1, Count: 69}},
|
||||
{name: "lunar/head", span: sarosSpan{Series: -54, First: -81564, Last: -61717, Member: 1, Count: 90}},
|
||||
{name: "lunar/4-head", span: sarosSpan{Series: 4, First: -57901, Last: -56340, Member: 1, Count: 86}},
|
||||
{name: "lunar/4-body", span: sarosSpan{Series: 4, First: -55894, Last: -38723, Member: 9, Count: 86}},
|
||||
{name: "lunar/tail", span: sarosSpan{Series: 287, First: 49211, Last: 64821, Member: 1, Count: 71}},
|
||||
}
|
||||
}
|
||||
|
||||
// 发布表必须与文档化的窗口、生成时的序列总数和若干金标准行一致。
|
||||
func TestSarosExtensionTableIsFresh(t *testing.T) {
|
||||
pinSarosWindowPolicy(t)
|
||||
@@ -64,17 +80,25 @@ func TestSarosExtensionTableIsFresh(t *testing.T) {
|
||||
rows int
|
||||
span []sarosSpan
|
||||
}{
|
||||
{name: "solar", phase: 0, rows: 159, span: solarSarosExtended[:]},
|
||||
{name: "lunar", phase: 1, rows: 249, span: lunarSarosExtended[:]},
|
||||
{name: "solar", phase: 0, rows: 159, span: sarosExtensionSpans(0)},
|
||||
{name: "lunar", phase: 1, rows: 249, span: sarosExtensionSpans(1)},
|
||||
} {
|
||||
if ephem.Active() == ephem.Semianalytic {
|
||||
switch table.phase {
|
||||
case 0:
|
||||
table.rows = 163
|
||||
case 1:
|
||||
table.rows = 252
|
||||
}
|
||||
}
|
||||
if len(table.span) != table.rows {
|
||||
t.Fatalf("%s table has %d rows, golden table has %d", table.name, len(table.span), table.rows)
|
||||
}
|
||||
validateSarosExtensionSpans(t, table.name, table.phase, table.span)
|
||||
}
|
||||
for _, golden := range sarosTableGoldens {
|
||||
for _, golden := range sarosTableGoldens() {
|
||||
found := false
|
||||
for _, span := range append(solarSarosExtended[:], lunarSarosExtended[:]...) {
|
||||
for _, span := range append(append([]sarosSpan{}, sarosExtensionSpans(0)...), sarosExtensionSpans(1)...) {
|
||||
if span == golden.span {
|
||||
found = true
|
||||
break
|
||||
@@ -121,7 +145,7 @@ type sarosGenerationEvent struct {
|
||||
|
||||
func TestGenerateSarosExtensionTables(t *testing.T) {
|
||||
if !sarosTableGenerationEnabled() {
|
||||
t.Skipf("set %s=1 and pass -saros-generate to rewrite saros_table_extended.go", sarosGenerateEnv)
|
||||
t.Skipf("set %s=1 and pass -saros-generate to rewrite the active model's Saros table", sarosGenerateEnv)
|
||||
}
|
||||
pinSarosWindowPolicy(t)
|
||||
startTT := timeToTTJDE(time.Date(-3000, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
@@ -129,17 +153,17 @@ func TestGenerateSarosExtensionTables(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintln(&buf, "// Code generated by go generate ./eclipse; DO NOT EDIT.")
|
||||
fmt.Fprintln(&buf, "\npackage eclipse")
|
||||
fmt.Fprintln(&buf, "\n// UTC astronomical years -3000 through +6000, including both end years.")
|
||||
fmt.Fprintln(&buf, "// Series numbers follow NASA's Saros-Inex rules. Members are computed with")
|
||||
fmt.Fprintln(&buf, "// Split-K solar geometry and the union of Danjon/Chauvenet lunar detections.")
|
||||
fmt.Fprintf(&buf, "const sarosExtendedStartTT = %.12f\nconst sarosExtendedEndTT = %.12f\n", startTT, endTT)
|
||||
suffix := "Legacy"
|
||||
if ephem.Active() == ephem.Semianalytic {
|
||||
suffix = "Semianalytic"
|
||||
}
|
||||
for phase, name := range []string{"solar", "lunar"} {
|
||||
events := scanSarosGenerationEvents(t, phase)
|
||||
assignSarosGenerationNumbers(t, events, phase)
|
||||
spans := buildSarosGenerationSpans(t, events, phase, startTT, endTT)
|
||||
// 写盘前先按发布口径校验,坏表不得进入仓库。
|
||||
validateSarosExtensionSpans(t, name, phase, spans)
|
||||
fmt.Fprintf(&buf, "\nvar %sSarosExtended = [...]sarosSpan{\n", name)
|
||||
fmt.Fprintf(&buf, "\nvar %sSarosExtended%s = [...]sarosSpan{\n", name, suffix)
|
||||
for _, span := range spans {
|
||||
fmt.Fprintf(&buf, "{Series: %d, First: %d, Last: %d, Member: %d, Count: %d},\n", span.Series, span.First, span.Last, span.Member, span.Count)
|
||||
}
|
||||
@@ -150,10 +174,14 @@ func TestGenerateSarosExtensionTables(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile("saros_table_extended.go", source, 0644); err != nil {
|
||||
filename := "saros_table_extended_legacy.go"
|
||||
if ephem.Active() == ephem.Semianalytic {
|
||||
filename = "saros_table_extended_semianalytic.go"
|
||||
}
|
||||
if err := os.WriteFile(filename, source, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("rewrote saros_table_extended.go; rerun TestSarosExtensionTableIsFresh and update the goldens")
|
||||
t.Logf("rewrote %s; rerun TestSarosExtensionTableIsFresh and update the goldens", filename)
|
||||
}
|
||||
|
||||
func scanSarosGenerationEvents(t *testing.T, phase int) []sarosGenerationEvent {
|
||||
|
||||
Reference in New Issue
Block a user