feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// svgElement 是一个已解析的 SVG 起始标签及其属性。
|
||||
type svgElement struct {
|
||||
tag string
|
||||
attrs map[string]string
|
||||
}
|
||||
|
||||
func parseSVGElements(t *testing.T, value string) []svgElement {
|
||||
t.Helper()
|
||||
decoder := xml.NewDecoder(strings.NewReader(value))
|
||||
elements := make([]svgElement, 0)
|
||||
for {
|
||||
token, err := decoder.Token()
|
||||
if err == io.EOF {
|
||||
return elements
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("chart is not valid XML: %v", err)
|
||||
}
|
||||
start, ok := token.(xml.StartElement)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
attrs := make(map[string]string, len(start.Attr))
|
||||
for _, attr := range start.Attr {
|
||||
attrs[attr.Name.Local] = attr.Value
|
||||
}
|
||||
elements = append(elements, svgElement{tag: start.Name.Local, attrs: attrs})
|
||||
}
|
||||
}
|
||||
|
||||
func svgAttributeFloat(attrs map[string]string, name string) (float64, bool) {
|
||||
value, ok := attrs[name]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
parsed, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return parsed, true
|
||||
}
|
||||
|
||||
// assertSVGElementsInCanvas 断言全部文本与矩形都落在画布内:版面一旦按硬编码坐标排版,
|
||||
// 尺寸不足时元素会静默跑到画布外而不是报错。
|
||||
func assertSVGElementsInCanvas(t *testing.T, value string, width, height int) {
|
||||
t.Helper()
|
||||
texts, rects := 0, 0
|
||||
for _, element := range parseSVGElements(t, value) {
|
||||
switch element.tag {
|
||||
case "text":
|
||||
texts++
|
||||
x, hasX := svgAttributeFloat(element.attrs, "x")
|
||||
y, hasY := svgAttributeFloat(element.attrs, "y")
|
||||
if hasX && (x < 0 || x > float64(width)) {
|
||||
t.Fatalf("text x=%.3f outside the %d-wide canvas: %v", x, width, element.attrs)
|
||||
}
|
||||
if hasY && (y < 0 || y > float64(height)) {
|
||||
t.Fatalf("text y=%.3f outside the %d-high canvas: %v", y, height, element.attrs)
|
||||
}
|
||||
case "rect":
|
||||
x, hasX := svgAttributeFloat(element.attrs, "x")
|
||||
y, hasY := svgAttributeFloat(element.attrs, "y")
|
||||
w, hasW := svgAttributeFloat(element.attrs, "width")
|
||||
h, hasH := svgAttributeFloat(element.attrs, "height")
|
||||
if !hasX || !hasY || !hasW || !hasH {
|
||||
continue
|
||||
}
|
||||
rects++
|
||||
if x < -0.01 || y < -0.01 || x+w > float64(width)+0.01 || y+h > float64(height)+0.01 {
|
||||
t.Fatalf("rect (%.3f,%.3f,%.3f,%.3f) outside the %dx%d canvas", x, y, w, h, width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
if texts == 0 || rects == 0 {
|
||||
t.Fatalf("chart carries %d texts and %d rects, want a full page", texts, rects)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user