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