2bf8478639
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
90 lines
3.3 KiB
Go
90 lines
3.3 KiB
Go
package svg
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"b612.me/astro/moon"
|
|
)
|
|
|
|
// 掩甚时刻等时线由路径选项请求,渲染器只在路径携带时绘制。
|
|
func TestPlanetOccultationSVGDrawsGreatestTimeIsochrones(t *testing.T) {
|
|
start := time.Date(2025, time.February, 1, 0, 0, 0, 0, time.UTC)
|
|
end := time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)
|
|
render := func(step time.Duration) string {
|
|
t.Helper()
|
|
paths, err := moon.FindPlanetOccultationPaths(start, end, moon.OccultationSaturn, moon.OccultationPathOptions{
|
|
Step: 2 * time.Minute, DisableFootprints: true, GreatestTimeStep: step,
|
|
})
|
|
if err != nil || len(paths) == 0 {
|
|
t.Fatalf("no occultation path: %v", err)
|
|
}
|
|
if step > 0 && len(paths[0].GreatestTimeContours) == 0 {
|
|
t.Fatal("path carries no isochrones")
|
|
}
|
|
rendered, renderErr := PlanetOccultationPathSVG(paths[0], StarOccultationSVGOptions{
|
|
Width: 1200, Height: 800, Location: time.UTC,
|
|
})
|
|
if renderErr != nil {
|
|
t.Fatalf("render: %v", renderErr)
|
|
}
|
|
return rendered
|
|
}
|
|
|
|
withIsochrones := render(30 * time.Minute)
|
|
if !strings.Contains(withIsochrones, `class="greatest-time-isoline"`) {
|
|
t.Fatal("isochrone paths missing")
|
|
}
|
|
if !strings.Contains(withIsochrones, `class="greatest-time-isoline-label"`) {
|
|
t.Fatal("isochrone labels missing")
|
|
}
|
|
if strings.Contains(render(0), "greatest-time-isoline") {
|
|
t.Fatal("a zero step must not draw isochrones")
|
|
}
|
|
}
|
|
|
|
// 等时线也要走路径校验:坏支路必须在渲染前被拦住,而不是画出 NaN 折线。
|
|
func TestPlanetOccultationSVGRejectsBrokenGreatestTimeIsochrones(t *testing.T) {
|
|
paths, err := moon.FindPlanetOccultationPaths(
|
|
time.Date(2025, time.February, 1, 0, 0, 0, 0, time.UTC),
|
|
time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC), moon.OccultationSaturn,
|
|
moon.OccultationPathOptions{Step: 2 * time.Minute, DisableFootprints: true, GreatestTimeStep: 30 * time.Minute},
|
|
)
|
|
if err != nil || len(paths) == 0 || len(paths[0].GreatestTimeContours) == 0 {
|
|
t.Fatalf("no isochrone path: %v", err)
|
|
}
|
|
options := StarOccultationSVGOptions{Width: 1200, Height: 800, Location: time.UTC}
|
|
render := func(mutate func(path *moon.PlanetOccultationPath)) error {
|
|
path := paths[0]
|
|
path.GreatestTimeContours = append([]moon.OccultationGreatestTimeContour(nil), path.GreatestTimeContours...)
|
|
mutate(&path)
|
|
_, renderErr := PlanetOccultationPathSVG(path, options)
|
|
return renderErr
|
|
}
|
|
if err := render(func(path *moon.PlanetOccultationPath) {}); err != nil {
|
|
t.Fatalf("valid isochrones rejected: %v", err)
|
|
}
|
|
if err := render(func(path *moon.PlanetOccultationPath) {
|
|
path.GreatestTimeContours[0].JDE = 0
|
|
}); err == nil {
|
|
t.Fatal("zero JDE accepted")
|
|
}
|
|
if err := render(func(path *moon.PlanetOccultationPath) {
|
|
path.GreatestTimeContours[0].Segments[0][0].Longitude = math.NaN()
|
|
}); err == nil {
|
|
t.Fatal("NaN longitude accepted")
|
|
}
|
|
if err := render(func(path *moon.PlanetOccultationPath) {
|
|
path.GreatestTimeContours[0].Segments[0] = path.GreatestTimeContours[0].Segments[0][:1]
|
|
}); err == nil {
|
|
t.Fatal("single-point branch accepted")
|
|
}
|
|
if err := render(func(path *moon.PlanetOccultationPath) {
|
|
path.GreatestTimeContours[0].Segments[0][0].Time = path.GreatestTimeContours[0].Segments[0][0].Time.Add(2 * time.Second)
|
|
}); err == nil {
|
|
t.Fatal("branch point with a mismatched instant accepted")
|
|
}
|
|
}
|