feat: 新增月掩与日月食地理绘图并提升观测计算精度

- 新增月掩恒星和行星:支持搜索、掩甚点、全球掩带及固定地点轨迹计算
- 支持恒星星表坐标转换、有限盘面行星接触事件和月掩 SVG 输出
- 新增日月食及月掩全球投影图、时间标记和 GeoJSON 地理数据接口
- 扩展日食中心线、南北界及偏食足迹采样,支持极区投影
- 修正站心时角、月出月落、月球视半径、折射和恒星自行计算
- 优化内外行星事件搜索、边界选择、极端输入处理和计算稳定性
This commit is contained in:
2026-08-06 12:00:56 +08:00
parent 25dc7ac0bc
commit 9ee2163cc7
137 changed files with 21770 additions and 1746 deletions
+104 -24
View File
@@ -195,6 +195,9 @@ func venusElongationDerivativeN(jde, val float64, n int) float64 {
}
func venusConjunction(jde float64, next uint8) float64 {
if !isFiniteFloat(jde) {
return math.NaN()
}
queryTT := jde
direction := -1.0
if next == 1 {
@@ -203,13 +206,14 @@ func venusConjunction(jde float64, next uint8) float64 {
left := queryTT
leftVal := venusSunLongitudeDeltaN(left, venusEventSearchN)
if math.Abs(venusSunLongitudeDelta(queryTT)) <= 30.0/86400.0 {
exact := eventZeroRefine(left, 1.0, 0.000005, venusSunLongitudeDelta)
eventUT := TD2UT(exact, false)
if next == 0 && eventUTQueryBeforeOrEqual(eventUT, queryTT) {
return eventUT
}
if next == 1 && eventUTQueryAfterOrEqual(eventUT, queryTT) {
return eventUT
if exact, ok := venusConjunctionRefine(left, 1.0); ok {
eventUT := TD2UT(exact, false)
if next == 0 && eventUTQueryBeforeOrEqual(eventUT, queryTT) {
return eventUT
}
if next == 1 && eventUTQueryAfterOrEqual(eventUT, queryTT) {
return eventUT
}
}
}
const step = 8.0
@@ -219,12 +223,36 @@ func venusConjunction(jde float64, next uint8) float64 {
if leftVal == 0 || rightVal == 0 || leftVal*rightVal <= 0 {
center := (left + right) / 2.0
halfWindow := math.Abs(right-left) / 2.0
return TD2UT(eventZeroRefine(center, halfWindow, 0.000005, venusSunLongitudeDelta), false)
if exact, ok := venusConjunctionRefine(center, halfWindow); ok {
return TD2UT(exact, false)
}
return math.NaN()
}
left = right
leftVal = rightVal
}
return TD2UT(eventZeroRefine(queryTT, VENUS_S_PERIOD, 0.000005, venusSunLongitudeDelta), false)
// 640 天已经覆盖一个金星会合周期;仍无括号通常表示输入超出解析项的可靠范围。
// 继续按 5 微日扫描整个周期会产生数亿次星历计算,因此在这里有界失败。
// The 640-day directional scan already exceeds one Venus synodic period. If it
// still finds no bracket, fail in a bounded way instead of scanning hundreds
// of millions of five-microday samples across the full fallback window.
return math.NaN()
}
func venusConjunctionRefine(seed, halfWindow float64) (float64, bool) {
leftJD := seed - halfWindow
centerJD := seed
rightJD := seed + halfWindow
leftVal := venusSunLongitudeDelta(leftJD)
centerVal := venusSunLongitudeDelta(centerJD)
rightVal := venusSunLongitudeDelta(rightJD)
if !isFiniteFloat(leftVal) || !isFiniteFloat(centerVal) || !isFiniteFloat(rightVal) {
return math.NaN(), false
}
if _, _, _, _, ok := eventZeroBracket(leftJD, leftVal, centerJD, centerVal, rightJD, rightVal); !ok {
return math.NaN(), false
}
return eventZeroRefine(seed, halfWindow, 0.000005, venusSunLongitudeDelta), true
}
func venusConjunctionTypeAt(eventUT float64) bool {
@@ -289,6 +317,9 @@ func LastVenusSuperiorConjunction(jde float64) float64 {
func venusRetrograde(jde float64) float64 {
//0=last 1=next
if !isFiniteFloat(jde) {
return math.NaN()
}
lastHe := LastVenusConjunctionStrict(jde)
nextHe := NextVenusConjunctionStrict(jde)
nowSub := venusSunRADelta(jde)
@@ -297,23 +328,31 @@ func venusRetrograde(jde float64) float64 {
} else {
jde = lastHe + 10
}
for {
found := false
for i := 0; i < eventDirectionalSearchIterations; i++ {
nowSub := venusRADerivativeN(jde, 1.0/86400.0, venusEventSearchN)
if !isFiniteFloat(nowSub) {
return math.NaN()
}
if math.Abs(nowSub) > 0.5 {
jde += 5
continue
}
found = true
break
}
if !found {
return math.NaN()
}
JD1 := jde
for {
JD0 := JD1
var ok bool
JD1, ok = eventNewtonRefine(JD1, 20.0/86400.0, func(JD0 float64) float64 {
stDegree := venusRADerivative(JD0, 0.5/86400.0)
stDegreep := (venusRADerivative(JD0+10.0/86400.0, 0.5/86400.0) - venusRADerivative(JD0-10.0/86400.0, 0.5/86400.0)) / (20.0 / 86400.0)
JD1 = JD0 - stDegree/stDegreep
if math.Abs(JD1-JD0) <= 20.0/86400.0 {
break
}
return stDegree / stDegreep
})
if !ok {
return math.NaN()
}
min := eventZeroRefine(JD1, 10.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
return venusRADerivative(jd, 0.5/86400.0)
@@ -376,46 +415,62 @@ func venusRetrogradeToProgradeAroundInferior(inferior float64) float64 {
func NextVenusProgradeToRetrograde(jde float64) float64 {
inferior := NextVenusInferiorConjunction(jde)
for {
for i := 0; i < eventDirectionalSearchIterations; i++ {
date := venusProgradeToRetrogradeAroundInferior(inferior)
if !isFiniteFloat(date) {
return math.NaN()
}
if eventUTQueryAfterOrEqual(date, jde) {
return date
}
inferior = NextVenusInferiorConjunction(eventUTNextQueryTT(inferior))
}
return math.NaN()
}
func NextVenusRetrogradeToPrograde(jde float64) float64 {
inferior := LastVenusInferiorConjunction(jde)
for {
for i := 0; i < eventDirectionalSearchIterations; i++ {
date := venusRetrogradeToProgradeAroundInferior(inferior)
if !isFiniteFloat(date) {
return math.NaN()
}
if eventUTQueryAfterOrEqual(date, jde) {
return date
}
inferior = NextVenusInferiorConjunction(eventUTNextQueryTT(inferior))
}
return math.NaN()
}
func LastVenusProgradeToRetrograde(jde float64) float64 {
inferior := NextVenusInferiorConjunction(jde)
for {
for i := 0; i < eventDirectionalSearchIterations; i++ {
date := venusProgradeToRetrogradeAroundInferior(inferior)
if !isFiniteFloat(date) {
return math.NaN()
}
if eventUTQueryBeforeOrEqual(date, jde) {
return date
}
inferior = LastVenusInferiorConjunction(eventUTLastQueryTT(inferior))
}
return math.NaN()
}
func LastVenusRetrogradeToPrograde(jde float64) float64 {
inferior := LastVenusInferiorConjunction(jde)
for {
for i := 0; i < eventDirectionalSearchIterations; i++ {
date := venusRetrogradeToProgradeAroundInferior(inferior)
if !isFiniteFloat(date) {
return math.NaN()
}
if eventUTQueryBeforeOrEqual(date, jde) {
return date
}
inferior = LastVenusInferiorConjunction(eventUTLastQueryTT(inferior))
}
return math.NaN()
}
func VenusSunElongation(jde float64) float64 {
@@ -465,52 +520,77 @@ func venusWestElongationWindowContaining(jde float64) (float64, float64) {
}
func nextVenusGreatestElongationTyped(jde float64, east bool) float64 {
if !isFiniteFloat(jde) {
return math.NaN()
}
if east {
start, windowEnd := venusEastElongationWindowContaining(jde)
for {
for i := 0; i < eventDirectionalSearchIterations; i++ {
date := venusGreatestElongationInWindow(start, windowEnd)
if !isFiniteFloat(date) {
return math.NaN()
}
if eventUTQueryAfterOrEqual(date, jde) {
return date
}
nextInferior := NextVenusInferiorConjunction(eventUTNextQueryTT(windowEnd))
start, windowEnd = venusEastElongationWindowEndingAt(nextInferior)
}
return math.NaN()
}
start, windowEnd := venusWestElongationWindowContaining(jde)
for {
for i := 0; i < eventDirectionalSearchIterations; i++ {
date := venusGreatestElongationInWindow(start, windowEnd)
if !isFiniteFloat(date) {
return math.NaN()
}
if eventUTQueryAfterOrEqual(date, jde) {
return date
}
nextSuperior := NextVenusSuperiorConjunction(eventUTNextQueryTT(windowEnd))
start, windowEnd = venusWestElongationWindowEndingAt(nextSuperior)
}
return math.NaN()
}
func lastVenusGreatestElongationTyped(jde float64, east bool) float64 {
if !isFiniteFloat(jde) {
return math.NaN()
}
if east {
start, windowEnd := venusEastElongationWindowContaining(jde)
for {
for i := 0; i < eventDirectionalSearchIterations; i++ {
date := venusGreatestElongationInWindow(start, windowEnd)
if !isFiniteFloat(date) {
return math.NaN()
}
if eventUTQueryBeforeOrEqual(date, jde) {
return date
}
prevInferior := LastVenusInferiorConjunction(eventUTLastQueryTT(start))
start, windowEnd = venusEastElongationWindowEndingAt(prevInferior)
}
return math.NaN()
}
start, windowEnd := venusWestElongationWindowContaining(jde)
for {
for i := 0; i < eventDirectionalSearchIterations; i++ {
date := venusGreatestElongationInWindow(start, windowEnd)
if !isFiniteFloat(date) {
return math.NaN()
}
if eventUTQueryBeforeOrEqual(date, jde) {
return date
}
prevSuperior := LastVenusSuperiorConjunction(eventUTLastQueryTT(start))
start, windowEnd = venusWestElongationWindowEndingAt(prevSuperior)
}
return math.NaN()
}
func venusGreatestElongation(jde float64) float64 {
if !isFiniteFloat(jde) {
return math.NaN()
}
east := venusSunRADelta(jde) > 0
if east {
return nextVenusGreatestElongationTyped(jde, true)