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