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
+76 -30
View File
@@ -167,7 +167,79 @@ func moonMaximumDeclinationEvent(k int, coeffs moonMaxDeclinationCoefficients, c
}
func moonMaximumDeclinationSearch(jd float64, coeffs moonMaxDeclinationCoefficients, direction int, includeCurrent bool) DeclinationEvent {
cfg := apsisSearchConfig{
if !finite(jd) {
// 非有限查询算不出周期序号(centerK 会把 NaN/±Inf 压成 MinInt64,种子外推到 1e40 后步长小于一个 ULP)。
return DeclinationEvent{}
}
cfg := moonMaximumDeclinationSearchConfig(coeffs)
centerK := moonMaximumDeclinationOffset(jd, coeffs)
step := 1
if direction < 0 {
step = -1
}
event := moonMaximumDeclinationEvent(centerK, coeffs, cfg)
// 事件时刻随周期序号单调递增:该方向上最近的候选就是 centerK 沿该方向第一个满足方向不变量
// 的周期;命中后只需再回退检查更早的周期是否同样满足,可达范围仍旧是 ±3 个周期。
if !moonMaximumDeclinationMatchesDirection(event.JDE-jd, direction, includeCurrent) {
for offset := 1; offset <= moonMaxDeclinationSearchSpan; offset++ {
event = moonMaximumDeclinationEvent(centerK+step*offset, coeffs, cfg)
if moonMaximumDeclinationMatchesDirection(event.JDE-jd, direction, includeCurrent) {
return event
}
}
return DeclinationEvent{}
}
for offset := 1; offset <= moonMaxDeclinationSearchSpan; offset++ {
previous := moonMaximumDeclinationEvent(centerK-step*offset, coeffs, cfg)
if !moonMaximumDeclinationMatchesDirection(previous.JDE-jd, direction, includeCurrent) {
break
}
event = previous
}
return event
}
func moonClosestMaximumDeclination(jd float64, coeffs moonMaxDeclinationCoefficients) DeclinationEvent {
if !finite(jd) {
return DeclinationEvent{}
}
cfg := moonMaximumDeclinationSearchConfig(coeffs)
centerK := moonMaximumDeclinationOffset(jd, coeffs)
center := moonMaximumDeclinationEvent(centerK, coeffs, cfg)
var last, next DeclinationEvent
if center.JDE <= jd {
last = center
next = moonMaximumDeclinationAround(jd, coeffs, cfg, centerK, 1, false)
} else {
next = center
last = moonMaximumDeclinationAround(jd, coeffs, cfg, centerK, -1, true)
}
lastDistance := math.Abs(jd - last.JDE)
nextDistance := math.Abs(next.JDE - jd)
if lastDistance <= nextDistance {
return last
}
return next
}
// moonMaximumDeclinationAround 从 centerK 沿 step 方向找第一个满足方向不变量的周期;±3 个周期内没有就返回零事件。
func moonMaximumDeclinationAround(jd float64, coeffs moonMaxDeclinationCoefficients, cfg apsisSearchConfig,
centerK, step int, includeCurrent bool) DeclinationEvent {
direction := 1
if step < 0 {
direction = -1
}
for offset := 1; offset <= moonMaxDeclinationSearchSpan; offset++ {
event := moonMaximumDeclinationEvent(centerK+step*offset, coeffs, cfg)
if moonMaximumDeclinationMatchesDirection(event.JDE-jd, direction, includeCurrent) {
return event
}
}
return DeclinationEvent{}
}
func moonMaximumDeclinationSearchConfig(coeffs moonMaxDeclinationCoefficients) apsisSearchConfig {
return apsisSearchConfig{
bracketHalfWidth: moonApsisBracketHalfWidth,
sampleStep: moonApsisSampleStep,
derivativeStep: moonApsisDerivativeStep,
@@ -175,37 +247,11 @@ func moonMaximumDeclinationSearch(jd float64, coeffs moonMaxDeclinationCoefficie
maxIterations: moonApsisMaxIterations,
maximize: coeffs.sign > 0,
}
targetTT := TD2UT(jd, true)
centerK := int(math.Round((targetTT - coeffs.JDE0) / moonMaxDeclinationMeanMonthDays))
found := false
bestDistance := math.Inf(1)
var best DeclinationEvent
for offset := -moonMaxDeclinationSearchSpan; offset <= moonMaxDeclinationSearchSpan; offset++ {
event := moonMaximumDeclinationEvent(centerK+offset, coeffs, cfg)
delta := event.JDE - jd
if !moonMaximumDeclinationMatchesDirection(delta, direction, includeCurrent) {
continue
}
distance := math.Abs(delta)
if !found || distance < bestDistance || (distance == bestDistance && moonMaximumDeclinationEarlier(event, best)) {
best = event
bestDistance = distance
found = true
}
}
return best
}
func moonClosestMaximumDeclination(jd float64, coeffs moonMaxDeclinationCoefficients) DeclinationEvent {
last := moonMaximumDeclinationSearch(jd, coeffs, -1, true)
next := moonMaximumDeclinationSearch(jd, coeffs, 1, false)
lastDistance := math.Abs(jd - last.JDE)
nextDistance := math.Abs(next.JDE - jd)
if lastDistance <= nextDistance {
return last
}
return next
// moonMaximumDeclinationOffset 查询时刻落在哪个平均周期(种子多项式的中心序号)。
func moonMaximumDeclinationOffset(jd float64, coeffs moonMaxDeclinationCoefficients) int {
return int(math.Round((TD2UT(jd, true) - coeffs.JDE0) / moonMaxDeclinationMeanMonthDays))
}
func moonMaximumDeclinationMatchesDirection(delta float64, direction int, includeCurrent bool) bool {