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
+54
View File
@@ -6,6 +6,10 @@ const (
eventNewtonMaxIterations = 24
eventDirectionalSearchIterations = 128
eventRiseSetScanStep = 1.0 / 1440
// stationDerivativeStepDay 「留」精修用的中心差分步长(天)。
// 0.5 秒级的步长会被浮点相消噪声支配(实测外行星留误差可达 42 s);
// 0.01 天(14.4 分钟)实测误差 <0.05 s,且求值次数不变。
stationDerivativeStepDay = 0.01
)
func isFiniteFloat(value float64) bool {
@@ -121,6 +125,56 @@ func eventDirectionalBracketRefine(leftJD, rightJD, leftValue, rightValue float6
return (leftJD + rightJD) / 2
}
// eventBracketSecantRoot 在已知异号的括号内用割线法(带中点兜底)求根。
//
// 与 eventDirectionalBracketRefine(固定 48 次二分)相比,割线法通常 5~8 次求值即可达到
// 亚秒精度,适合「括号由廉价截断级数给出、抛光必须用全项级数」的两段式搜索。
// 括号每一步都收缩,因此不会跑到括号外;括号端点同号或出现非有限值时返回 false。
func eventBracketSecantRoot(leftJD, rightJD, leftValue, rightValue, tolerance float64,
fn func(float64) float64) (float64, bool) {
if leftValue == 0 {
return leftJD, true
}
if rightValue == 0 {
return rightJD, true
}
if !isFiniteFloat(leftValue) || !isFiniteFloat(rightValue) || leftValue*rightValue > 0 {
return math.NaN(), false
}
if !isFiniteFloat(tolerance) || tolerance <= 0 {
tolerance = 0.5 / 86400.0
}
bestJD := (leftJD + rightJD) / 2
for i := 0; i < 48; i++ {
candidateJD := (leftJD + rightJD) / 2
if rightValue != leftValue {
secantJD := rightJD - rightValue*(rightJD-leftJD)/(rightValue-leftValue)
if secantJD > leftJD && secantJD < rightJD {
candidateJD = secantJD
}
}
candidateValue := fn(candidateJD)
if !isFiniteFloat(candidateValue) {
return math.NaN(), false
}
bestJD = candidateJD
if candidateValue == 0 || math.Abs(rightJD-leftJD) <= tolerance {
return candidateJD, true
}
if (leftValue < 0) == (candidateValue < 0) {
leftJD, leftValue = candidateJD, candidateValue
continue
}
rightJD, rightValue = candidateJD, candidateValue
}
if math.Abs(rightJD-leftJD) <= tolerance {
return bestJD, true
}
// 48 次迭代后括号仍宽于容差(例如容差低于该儒略日的 ULP):调用方把 ok 当作
// “已抛光到容差”,这里必须报 false,不能返回一个精度未达标的时刻。
return bestJD, false
}
func eventFixedScanRefine(seed, halfWindow, step float64, fn func(float64) float64) float64 {
start := seed - halfWindow
bestJD := start