2bf8478639
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
118 lines
4.3 KiB
Go
118 lines
4.3 KiB
Go
package svg
|
||
|
||
import (
|
||
"errors"
|
||
"strconv"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
// 本文件钉住全球掩带图的画布下限与"所有文本都在画布内":480x360、600x400 曾因图例压页脚、经纬刻度出框而被放行。
|
||
|
||
func TestOccultationMapRejectsCanvasBelowMinimum(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
width, height int
|
||
}{
|
||
{name: "reference too small", width: 480, height: 360},
|
||
{name: "legend footer collision", width: 600, height: 400},
|
||
{name: "width just below", width: 639, height: 480},
|
||
{name: "height just below", width: 640, height: 479},
|
||
}
|
||
starPath := occultationTestStarPath(t)
|
||
planetPath := occultationTestSaturnPath(t)
|
||
for _, test := range tests {
|
||
t.Run(test.name, func(t *testing.T) {
|
||
options := StarOccultationSVGOptions{Width: test.width, Height: test.height}
|
||
if _, err := StarOccultationPathSVG(starPath, options); !errors.Is(err, ErrInvalidStarOccultationSVGOptions) {
|
||
t.Fatalf("stellar %dx%d error = %v, want ErrInvalidStarOccultationSVGOptions", test.width, test.height, err)
|
||
}
|
||
if _, err := PlanetOccultationPathSVG(planetPath, options); !errors.Is(err, ErrInvalidPlanetOccultationSVGOptions) {
|
||
t.Fatalf("planetary %dx%d error = %v, want ErrInvalidPlanetOccultationSVGOptions", test.width, test.height, err)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestOccultationMapKeepsEveryTextInsideCanvas(t *testing.T) {
|
||
sizes := [][2]int{{640, 480}, {800, 600}, {1200, 800}, {1000, 1414}}
|
||
projections := []MapProjection{MapProjectionAuto, MapProjectionNorthPolar, MapProjectionOrthographic}
|
||
starPath := occultationTestStarPath(t)
|
||
planetPath := occultationTestSaturnPath(t)
|
||
for _, size := range sizes {
|
||
for _, projection := range projections {
|
||
name := strconv.Itoa(size[0]) + "x" + strconv.Itoa(size[1]) + "-" + string(projection)
|
||
t.Run(name, func(t *testing.T) {
|
||
options := StarOccultationSVGOptions{
|
||
Width: size[0], Height: size[1], Location: time.UTC, Projection: projection,
|
||
}
|
||
diagrams := map[string]string{}
|
||
star, err := StarOccultationPathSVG(starPath, options)
|
||
if err != nil {
|
||
t.Fatalf("stellar render: %v", err)
|
||
}
|
||
diagrams["stellar"] = star
|
||
planet, err := PlanetOccultationPathSVG(planetPath, options)
|
||
if err != nil {
|
||
t.Fatalf("planetary render: %v", err)
|
||
}
|
||
diagrams["planetary"] = planet
|
||
for label, diagram := range diagrams {
|
||
texts := occultationSVGTexts(t, diagram)
|
||
if len(texts) == 0 {
|
||
t.Fatalf("%s SVG has no text", label)
|
||
}
|
||
if outside := occultationSVGTextsOutsideCanvas(texts, float64(size[0]), float64(size[1])); len(outside) > 0 {
|
||
t.Fatalf("%s SVG has %d texts outside %s: %v", label, len(outside), name, outside)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestOccultationMapLegendDoesNotOverlapFooter(t *testing.T) {
|
||
sizes := [][2]int{{640, 480}, {800, 600}, {1200, 800}}
|
||
starPath := occultationTestStarPath(t)
|
||
planetPath := occultationTestSaturnPath(t)
|
||
for _, size := range sizes {
|
||
t.Run(strconv.Itoa(size[0])+"x"+strconv.Itoa(size[1]), func(t *testing.T) {
|
||
options := StarOccultationSVGOptions{Width: size[0], Height: size[1], Location: time.UTC}
|
||
for _, item := range []struct {
|
||
label string
|
||
path func() (string, error)
|
||
}{
|
||
{label: "stellar", path: func() (string, error) { return StarOccultationPathSVG(starPath, options) }},
|
||
{label: "planetary", path: func() (string, error) { return PlanetOccultationPathSVG(planetPath, options) }},
|
||
} {
|
||
diagram, err := item.path()
|
||
if err != nil {
|
||
t.Fatalf("%s render: %v", item.label, err)
|
||
}
|
||
texts := occultationSVGTexts(t, diagram)
|
||
legend := []occultationSVGText{}
|
||
footer := []occultationSVGText{}
|
||
for _, text := range texts {
|
||
if text.class == "occultation-legend-text" {
|
||
legend = append(legend, text)
|
||
}
|
||
if text.parent == "svg" && text.class == "" && text.fontSize == 11 && text.x == 40 {
|
||
footer = append(footer, text)
|
||
}
|
||
}
|
||
if len(legend) < 3 || len(footer) == 0 {
|
||
t.Fatalf("%s SVG legend=%d footer=%d, want both present", item.label, len(legend), len(footer))
|
||
}
|
||
for _, legendText := range legend {
|
||
for _, footerText := range footer {
|
||
if occultationSVGTextBox(legendText)[3] > occultationSVGTextBox(footerText)[1] {
|
||
t.Fatalf("%s legend %q overlaps footer %q at %dx%d",
|
||
item.label, legendText.value, footerText.value, size[0], size[1])
|
||
}
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|