Files
astro/basic/lunar_eclipse_limb.go
T
b612 044f2f4381 feat: 引入可切换星历模型并扩展天象观测链路
- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算
- 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询
- 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出
- 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解
- 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤
- 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持
- 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理
- 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
2026-10-15 23:54:15 +08:00

148 lines
4.9 KiB
Go

package basic
import "math"
// lunarEclipseLimbMaxShiftDays 是月缘精修允许偏离圆月面接触的上限(10 分钟),超出视为不收敛并保留原值。
const lunarEclipseLimbMaxShiftDays = 600.0 / 86400.0
// lunarEclipseLimbStepDays 是割线法的初始步长(1 秒)。
const lunarEclipseLimbStepDays = 1.0 / 86400.0
// LunarEclipseWithLunarLimb 用月缘剖面精修月食接触时刻,食甚、食分与食型仍按圆月面 / refine lunar eclipse contacts with a lunar limb profile.
func LunarEclipseWithLunarLimb(result LunarEclipseResult, limb LunarLimb, inst *Instance) LunarEclipseResult {
if limb == nil {
return result
}
original := result
shadowModel := lunarEclipseShadowDanjon
if result.ShadowModel == LunarEclipseShadowModelChauvenet {
shadowModel = lunarEclipseShadowChauvenet
}
evaluate := func(jde float64) lunarShadowState {
return computeLunarShadowStateWithInstance(jde, shadowModel, inst)
}
refine := func(seed float64, kind lunarEclipseLimbContact) float64 {
if !finite(seed) {
return seed
}
return refineLunarEclipseLimbContact(seed, kind, limb, evaluate, inst)
}
if result.HasPenumbral {
result.PenumbralStart = refine(result.PenumbralStart, lunarEclipseLimbPenumbral)
result.PenumbralEnd = refine(result.PenumbralEnd, lunarEclipseLimbPenumbral)
}
if result.HasPartial {
result.PartialStart = refine(result.PartialStart, lunarEclipseLimbUmbral)
result.PartialEnd = refine(result.PartialEnd, lunarEclipseLimbUmbral)
}
if result.HasTotal {
result.TotalStart = refine(result.TotalStart, lunarEclipseLimbTotal)
result.TotalEnd = refine(result.TotalEnd, lunarEclipseLimbTotal)
}
result.LunarLimbApplied = lunarEclipseLimbChanged(result, original)
return result
}
func lunarEclipseLimbChanged(result, original LunarEclipseResult) bool {
for _, pair := range [][2]float64{
{result.PenumbralStart, original.PenumbralStart},
{result.PenumbralEnd, original.PenumbralEnd},
{result.PartialStart, original.PartialStart},
{result.PartialEnd, original.PartialEnd},
{result.TotalStart, original.TotalStart},
{result.TotalEnd, original.TotalEnd},
} {
if limbTimeChanged(pair[0], pair[1]) {
return true
}
}
return false
}
type lunarEclipseLimbContact int
const (
lunarEclipseLimbPenumbral lunarEclipseLimbContact = iota
lunarEclipseLimbUmbral
lunarEclipseLimbTotal
)
// refineLunarEclipseLimbContact 以圆月面接触为初值用割线法求月缘间隙的零点,不收敛时返回初值。
func refineLunarEclipseLimbContact(seed float64, kind lunarEclipseLimbContact, limb LunarLimb, evaluate lunarShadowEvaluator, inst *Instance) float64 {
t0, t1 := seed, seed+lunarEclipseLimbStepDays
g0, ok0 := lunarEclipseLimbGap(t0, kind, limb, evaluate, inst)
g1, ok1 := lunarEclipseLimbGap(t1, kind, limb, evaluate, inst)
if !ok0 || !ok1 {
return seed
}
for i := 0; i < 12; i++ {
if g1 == g0 {
return seed
}
t2 := t1 - g1*(t1-t0)/(g1-g0)
if !finite(t2) || math.Abs(t2-seed) > lunarEclipseLimbMaxShiftDays {
return seed
}
if math.Abs(t2-t1) < 1e-9 {
return t2
}
g2, ok := lunarEclipseLimbGap(t2, kind, limb, evaluate, inst)
if !ok {
return seed
}
t0, g0, t1, g1 = t1, g1, t2, g2
}
return seed
}
// lunarEclipseLimbGap 是月缘版接触间隙(弧度),零点即接触。
func lunarEclipseLimbGap(jde float64, kind lunarEclipseLimbContact, limb LunarLimb, evaluate lunarShadowEvaluator, inst *Instance) (float64, bool) {
state := evaluate(jde)
moonRA, moonDec := HMoonGeocentricApparentRaDecWithInstance(jde, inst)
sunRA, sunDec := HSunApparentRaDecWithInstance(jde, inst)
moon := lunarEclipseLimbVector(moonRA, moonDec, HMoonAwayWithInstance(jde, inst))
shadow := lunarEclipseLimbVector(sunRA+180, -sunDec, 1)
if !finite(state.moonRadiusRad) || state.moonRadiusRad <= 0 {
return 0, false
}
frame := newLunarLimbFrame(limb, jde, moon, inst)
view, ok := frame.viewToward(moon, shadow)
if !ok {
return 0, false
}
separation := math.Hypot(state.x, state.y)
contact := lunarLimbContact{
frame: &frame,
view: view,
separation: separation,
anglePerKM: state.moonRadiusRad / lunarMoonRadiusScale,
}
switch kind {
case lunarEclipseLimbPenumbral:
contact.targetRadius = state.penumbraRadiusRad
default:
contact.targetRadius = state.umbraRadiusRad
}
// 半影、本影外切取全方向最外的月缘;全食内切取全方向最靠外、最后进入本影的月缘。
var limbRad float64
if kind == lunarEclipseLimbTotal {
limbRad, ok = contact.innerRadius(false)
} else {
limbRad, ok = contact.outerRadius()
}
if !ok {
return 0, false
}
if kind == lunarEclipseLimbTotal {
return separation - math.Abs(limbRad-contact.targetRadius), true
}
return separation - limbRad - contact.targetRadius, true
}
func lunarEclipseLimbVector(raDeg, decDeg, distance float64) [3]float64 {
sinRA, cosRA := math.Sincos(raDeg * rad)
sinDec, cosDec := math.Sincos(decDeg * rad)
return [3]float64{distance * cosDec * cosRA, distance * cosDec * sinRA, distance * sinDec}
}