feat: 完善日月食与月掩几何链路并扩展历法接口

- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑
- 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口
- 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界
- 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名
- 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验
- 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口
- 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
This commit is contained in:
2026-09-17 12:27:40 +08:00
parent 9ee2163cc7
commit 2bf8478639
428 changed files with 85981 additions and 7998 deletions
@@ -0,0 +1,69 @@
package svg
import (
"strings"
"testing"
"time"
)
// EventsTitle 覆盖“全球阶段”数据块的标题;空值使用该块的本地化默认标题。
func TestSolarEclipseEventsTitleLocalizesAndOverrides(t *testing.T) {
for _, test := range []struct {
name string
language string
title string
want string
}{
{name: "zh default", want: "食甚点的地方情况"},
{name: "zh override", title: "全球阶段(自定义)", want: "全球阶段(自定义)"},
{name: "en default", language: "en", want: "Local circumstances at greatest"},
{name: "en override", language: "en", title: "Global phases (custom)", want: "Global phases (custom)"},
} {
t.Run(test.name, func(t *testing.T) {
_, circumstances, _ := solarEclipseContactTitles(SolarEclipseMapSVGOptions{
Language: test.language, EventsTitle: test.title,
})
if circumstances != test.want {
t.Fatalf("global-phase block title = %q, want %q", circumstances, test.want)
}
})
}
// 两种版式都要把标题写进产物,横版与竖版各验证一次。
for _, layout := range []struct {
name string
width, height int
language string
}{
{name: "landscape", width: 960, height: 640},
{name: "portrait", width: 800, height: 820, language: "en"},
} {
t.Run("rendered/"+layout.name, func(t *testing.T) {
title := "全球阶段(自定义)"
unwanted := "食甚点的地方情况"
if layout.language == "en" {
title = "Global phases (custom)"
unwanted = "Local circumstances at greatest"
}
rendered, ok := SolarEclipseMapSVG(
time.Date(2024, time.April, 8, 0, 0, 0, 0, time.UTC),
SolarEclipseMapSVGOptions{
Width: layout.width, Height: layout.height, Location: time.UTC,
PartialStep: 10 * time.Minute, Language: layout.language, EventsTitle: title,
},
)
if !ok {
t.Fatal("missing 2024-04-08 map")
}
if !strings.Contains(rendered, title) {
t.Fatalf("map is missing the events title %q", title)
}
if strings.Contains(rendered, unwanted) {
t.Fatalf("map still carries the replaced events title %q", unwanted)
}
if err := validateEclipseMapXML(rendered); err != nil {
t.Fatalf("map is not valid XML: %v", err)
}
})
}
}