feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"b612.me/astro/moon"
|
||||
)
|
||||
|
||||
// 本文件钉住紧凑掩带合并缓存的契约:同输入复用只读结果、不同几何不会串味、产物逐字节确定。
|
||||
|
||||
func occultationTestCompactSweep(t *testing.T) occultationFootprintSweep {
|
||||
t.Helper()
|
||||
path := occultationTestSaturnPath(t)
|
||||
if len(path.PartialBandFootprints) == 0 {
|
||||
t.Fatal("fixture carries no compact band footprints")
|
||||
}
|
||||
return occultationFootprintSweep{
|
||||
footprints: path.PartialBandFootprints,
|
||||
contours: path.PartialBandContours,
|
||||
visibilityContours: path.PartialVisibilityContours,
|
||||
northern: path.NorthernLimit,
|
||||
southern: path.SouthernLimit,
|
||||
curves: path.RiseSetCurves,
|
||||
compactBand: true,
|
||||
kind: partialOccultationFootprintSweep,
|
||||
}
|
||||
}
|
||||
|
||||
func TestOccultationBandCacheReusesIdenticalGeometry(t *testing.T) {
|
||||
sweep := occultationTestCompactSweep(t)
|
||||
first, firstAuthoritative, err := sweep.mergedBandPolygons()
|
||||
if err != nil {
|
||||
t.Fatalf("first mergedBandPolygons() error = %v", err)
|
||||
}
|
||||
if len(first) == 0 {
|
||||
t.Fatal("compact band merge produced no polygon")
|
||||
}
|
||||
second, secondAuthoritative, err := sweep.mergedBandPolygons()
|
||||
if err != nil {
|
||||
t.Fatalf("second mergedBandPolygons() error = %v", err)
|
||||
}
|
||||
if len(second) != len(first) || &second[0][0] != &first[0][0] {
|
||||
t.Fatal("identical band geometry was merged again instead of reusing the cached polygons")
|
||||
}
|
||||
if secondAuthoritative != firstAuthoritative {
|
||||
t.Fatalf("cached authoritative flag = %v, want %v", secondAuthoritative, firstAuthoritative)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOccultationBandCacheSeparatesDifferentGeometry(t *testing.T) {
|
||||
sweep := occultationTestCompactSweep(t)
|
||||
first, _, err := sweep.mergedBandPolygons()
|
||||
if err != nil {
|
||||
t.Fatalf("first mergedBandPolygons() error = %v", err)
|
||||
}
|
||||
mutated := sweep
|
||||
mutated.footprints = append([]moon.PlanetOccultationFootprint(nil), sweep.footprints...)
|
||||
last := len(mutated.footprints) - 1
|
||||
mutated.footprints[last].Polygons = append([][]moon.OccultationPathPoint(nil), mutated.footprints[last].Polygons...)
|
||||
polygon := append([]moon.OccultationPathPoint(nil), mutated.footprints[last].Polygons[0]...)
|
||||
polygon[0].Longitude += 1.5
|
||||
mutated.footprints[last].Polygons[0] = polygon
|
||||
second, _, err := mutated.mergedBandPolygons()
|
||||
if err != nil {
|
||||
t.Fatalf("mutated mergedBandPolygons() error = %v", err)
|
||||
}
|
||||
if len(second) > 0 && len(first) > 0 && &second[0][0] == &first[0][0] {
|
||||
t.Fatal("a shifted footprint reused the cached band polygons")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompactBandRenderIsDeterministic(t *testing.T) {
|
||||
path := occultationTestSaturnPath(t)
|
||||
options := StarOccultationSVGOptions{Width: 1200, Height: 800, Location: time.UTC}
|
||||
first, err := PlanetOccultationPathSVG(path, options)
|
||||
if err != nil {
|
||||
t.Fatalf("first render: %v", err)
|
||||
}
|
||||
second, err := PlanetOccultationPathSVG(path, options)
|
||||
if err != nil {
|
||||
t.Fatalf("second render: %v", err)
|
||||
}
|
||||
if first != second {
|
||||
t.Fatal("compact-band SVG changed between renders after the merge was cached")
|
||||
}
|
||||
if len(first) == 0 {
|
||||
t.Fatal("compact-band render is empty")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user