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
+227 -155
View File
@@ -12,6 +12,11 @@ const (
mercuryConjunctionDerivativeStepDay = 2e-5 * 36525.0
mercuryLightTimeDaysPerAU = 0.0057755183
mercuryEventSearchN = 16
mercuryStationWindowDays = 30.0
mercuryStationDerivativeStepDay = 0.01
mercuryStationCoarseStepDay = 2.0
mercuryStationHalfWindowDay = 2.0
mercuryStationMotionTolerance = 1e-3
)
type mercuryConjunctionLBR struct {
@@ -109,65 +114,29 @@ func mercuryConjunctionApproxTT(seed float64, inferior bool) float64 {
func mercuryConjunctionExactTT(seed float64, inferior bool) float64 {
estimateJD := mercuryConjunctionApproxTT(seed, inferior)
for {
converged := false
for i := 0; i < eventNewtonMaxIterations; i++ {
prevJD := estimateJD
longitudeDelta := mercuryConjunctionExactDelta(prevJD)
longitudeSlope := (mercuryConjunctionExactDelta(prevJD+0.000005) - mercuryConjunctionExactDelta(prevJD-0.000005)) / 0.00001
estimateJD = prevJD - longitudeDelta/longitudeSlope
if math.Abs(estimateJD-prevJD) <= 0.00001 {
nextJD := prevJD - longitudeDelta/longitudeSlope
estimateJD = nextJD
if math.Abs(nextJD-prevJD) <= 0.00001 {
converged = true
break
}
}
if !converged {
return math.NaN()
}
return estimateJD
}
func mercuryConjunctionLegacy(jde float64, next uint8) float64 {
//0=last 1=next
longitudeDeltaAt := func(jde float64) float64 {
return mercuryConjunctionExactDelta(jde)
}
currentDelta := longitudeDeltaAt(jde)
distanceTrend := math.Abs(longitudeDeltaAt(jde+1/86400.0)) - math.Abs(currentDelta)
if distanceTrend >= 0 && next == 1 && currentDelta > 0 {
jde += MERCURY_S_PERIOD/8.0 + 2
}
if distanceTrend >= 0 && next == 1 && currentDelta < 0 {
jde += MERCURY_S_PERIOD/6.0 + 2
}
if distanceTrend <= 0 && next == 0 && currentDelta < 0 {
jde -= MERCURY_S_PERIOD/8.0 + 2
}
if distanceTrend <= 0 && next == 0 && currentDelta > 0 {
jde -= MERCURY_S_PERIOD/6.0 + 2
}
for {
currentDelta := longitudeDeltaAt(jde)
distanceTrend := math.Abs(longitudeDeltaAt(jde+1/86400.0)) - math.Abs(currentDelta)
if math.Abs(currentDelta) > 12 || (distanceTrend > 0 && next == 1) || (distanceTrend < 0 && next == 0) {
if next == 1 {
jde += 2
} else {
jde -= 2
}
continue
}
break
}
estimateJD := jde
for {
prevJD := estimateJD
longitudeDelta := longitudeDeltaAt(prevJD)
longitudeSlope := (longitudeDeltaAt(prevJD+0.000005) - longitudeDeltaAt(prevJD-0.000005)) / 0.00001
estimateJD = prevJD - longitudeDelta/longitudeSlope
if math.Abs(estimateJD-prevJD) <= 0.00001 {
break
}
}
return TD2UT(estimateJD, false)
}
func mercuryConjunction(jde float64, next uint8) float64 {
//0=last 1=next
if !isFiniteFloat(jde) {
return math.NaN()
}
if math.Abs(mercuryConjunctionExactDelta(jde)) <= 30.0/86400.0 {
best := math.NaN()
consider := func(inferior bool) {
@@ -203,9 +172,14 @@ func mercuryConjunction(jde float64, next uint8) float64 {
if distanceTrend <= 0 && next == 0 && currentDelta > 0 {
jde -= MERCURY_S_PERIOD/6.0 + 2
}
for {
found := false
for i := 0; i < eventDirectionalSearchIterations; i++ {
currentDelta := mercuryConjunctionExactDelta(jde)
distanceTrend := math.Abs(mercuryConjunctionExactDelta(jde+1/86400.0)) - math.Abs(currentDelta)
nextDelta := mercuryConjunctionExactDelta(jde + 1/86400.0)
if !isFiniteFloat(currentDelta) || !isFiniteFloat(nextDelta) {
return math.NaN()
}
distanceTrend := math.Abs(nextDelta) - math.Abs(currentDelta)
if math.Abs(currentDelta) > 12 || (distanceTrend > 0 && next == 1) || (distanceTrend < 0 && next == 0) {
if next == 1 {
jde += 2
@@ -214,11 +188,18 @@ func mercuryConjunction(jde float64, next uint8) float64 {
}
continue
}
found = true
break
}
if !found {
return math.NaN()
}
inferior := mercuryConjunctionExactTT(jde, true)
superior := mercuryConjunctionExactTT(jde, false)
if !isFiniteFloat(inferior) || !isFiniteFloat(superior) {
return math.NaN()
}
best := inferior
if math.Abs(superior-jde) < math.Abs(inferior-jde) {
best = superior
@@ -274,51 +255,6 @@ func LastMercurySuperiorConjunction(jde float64) float64 {
return date
}
func mercuryRetrograde(jde float64) float64 {
//0=last 1=next
solarRADelta := func(jde float64) float64 {
sub := Limit360(MercuryApparentRa(jde) - SunApparentRa(jde))
if sub > 180 {
sub -= 360
}
if sub < -180 {
sub += 360
}
return sub
}
lastConjunction := mercuryConjunctionLegacy(jde, 0)
nextConjunction := mercuryConjunctionLegacy(jde, 1)
currentRADelta := solarRADelta(jde)
if currentRADelta > 0 {
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.0 * 3.5)
} else {
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.5)
}
for {
currentRate := mercuryRADerivative(jde, 1.0/86400.0)
if math.Abs(currentRate) > 0.55 {
jde += 2
continue
}
break
}
estimateJD := jde
for {
prevJD := estimateJD
rateValue := mercuryRADerivative(prevJD, 2.0/86400.0)
rateSlope := (mercuryRADerivative(prevJD+15.0/86400.0, 2.0/86400.0) - mercuryRADerivative(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
estimateJD = prevJD - rateValue/rateSlope
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
break
}
}
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
return mercuryRADerivative(jd, 0.5/86400.0)
})
//fmt.Println((bestJD - lastConjunction) / (nextConjunction - lastConjunction))
return TD2UT(bestJD, false)
}
func mercuryRADerivative(jde, delta float64) float64 {
sub := MercuryApparentRa(jde+delta) - MercuryApparentRa(jde-delta)
if sub > 180 {
@@ -330,55 +266,174 @@ func mercuryRADerivative(jde, delta float64) float64 {
return sub / (2 * delta)
}
func mercuryStationIsProgradeToRetrograde(eventUT float64) bool {
for _, offset := range []float64{0.25, 0.5, 1.0} {
before := mercuryRADerivative(eventUT-offset, 0.5/86400.0)
after := mercuryRADerivative(eventUT+offset, 0.5/86400.0)
if before > 0 && after < 0 {
func mercuryRADerivativeN(jde, delta float64, n int) float64 {
sub := MercuryApparentRaN(jde+delta, n) - MercuryApparentRaN(jde-delta, n)
if sub > 180 {
sub -= 360
}
if sub < -180 {
sub += 360
}
return sub / (2 * delta)
}
func mercuryStationInWindow(startTT, endTT float64) float64 {
bestJD := zeroEventInWindow(startTT, endTT, mercuryStationCoarseStepDay, mercuryStationHalfWindowDay, 30.0/86400.0, func(jd float64) float64 {
return mercuryRADerivativeN(jd, mercuryStationDerivativeStepDay, mercuryEventSearchN)
}, func(jd float64) float64 {
return mercuryRADerivative(jd, mercuryStationDerivativeStepDay)
})
return TD2UT(bestJD, false)
}
func mercuryStationBetween(startTT, endTT float64) bool {
if endTT < startTT {
startTT, endTT = endTT, startTT
}
if endTT-startTT <= 0 {
return false
}
if endTT-startTT > mercuryStationWindowDays {
return true
}
// 截断扫描足以判断单候选快速路径是否安全 / A truncated scan is enough to decide whether the one-candidate fast path is safe.
left := startTT
leftValue := mercuryRADerivativeN(left, mercuryStationDerivativeStepDay, mercuryEventSearchN)
for left < endTT {
right := left + mercuryStationCoarseStepDay
if right > endTT {
right = endTT
}
rightValue := mercuryRADerivativeN(right, mercuryStationDerivativeStepDay, mercuryEventSearchN)
if leftValue == 0 || leftValue*rightValue < 0 || rightValue == 0 {
return true
}
if before < 0 && after > 0 {
return false
}
left = right
leftValue = rightValue
}
before := mercuryRADerivative(eventUT-0.25, 0.5/86400.0)
after := mercuryRADerivative(eventUT+0.25, 0.5/86400.0)
return before > after
return false
}
func nextMercuryTypedStation(jde float64, progradeToRetrograde bool) float64 {
date := NextMercuryRetrogradeStrict(jde)
for mercuryStationIsProgradeToRetrograde(date) != progradeToRetrograde {
date = NextMercuryRetrogradeStrict(eventUTNextQueryTT(date))
}
return date
func mercuryProgradeToRetrogradeAroundInferior(inferiorUT float64) float64 {
inferiorTT := TD2UT(inferiorUT, true)
return mercuryStationInWindow(inferiorTT-mercuryStationWindowDays, inferiorTT)
}
func lastMercuryTypedStation(jde float64, progradeToRetrograde bool) float64 {
date := LastMercuryRetrogradeStrict(jde)
for mercuryStationIsProgradeToRetrograde(date) != progradeToRetrograde {
date = LastMercuryRetrogradeStrict(eventUTLastQueryTT(date))
func mercuryRetrogradeToProgradeAroundInferior(inferiorUT float64) float64 {
inferiorTT := TD2UT(inferiorUT, true)
return mercuryStationInWindow(inferiorTT, inferiorTT+mercuryStationWindowDays)
}
func NextMercuryProgradeToRetrograde(jde float64) float64 {
inferior := NextMercuryInferiorConjunction(jde)
date := mercuryProgradeToRetrogradeAroundInferior(inferior)
if eventUTQueryAfterOrEqual(date, jde) {
return date
}
return date
followingInferior := NextMercuryInferiorConjunction(eventUTNextQueryTT(inferior))
return mercuryProgradeToRetrogradeAroundInferior(followingInferior)
}
func NextMercuryRetrogradeToPrograde(jde float64) float64 {
inferior := LastMercuryInferiorConjunction(jde)
date := mercuryRetrogradeToProgradeAroundInferior(inferior)
if eventUTQueryAfterOrEqual(date, jde) {
return date
}
nextInferior := NextMercuryInferiorConjunction(eventUTNextQueryTT(inferior))
return mercuryRetrogradeToProgradeAroundInferior(nextInferior)
}
func LastMercuryProgradeToRetrograde(jde float64) float64 {
inferior := NextMercuryInferiorConjunction(jde)
date := mercuryProgradeToRetrogradeAroundInferior(inferior)
if eventUTQueryBeforeOrEqual(date, jde) {
return date
}
previousInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(inferior))
return mercuryProgradeToRetrogradeAroundInferior(previousInferior)
}
func LastMercuryRetrogradeToPrograde(jde float64) float64 {
inferior := LastMercuryInferiorConjunction(jde)
date := mercuryRetrogradeToProgradeAroundInferior(inferior)
if eventUTQueryBeforeOrEqual(date, jde) {
return date
}
previousInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(inferior))
return mercuryRetrogradeToProgradeAroundInferior(previousInferior)
}
func nextMercuryRetrogradeFromTyped(jde float64) float64 {
p2r := NextMercuryProgradeToRetrograde(jde)
r2p := NextMercuryRetrogradeToPrograde(jde)
if p2r < r2p {
return p2r
}
return r2p
}
func NextMercuryRetrograde(jde float64) float64 {
date := mercuryRetrograde(jde)
if !eventUTQueryAfterOrEqual(date, jde) {
nextConjunction := NextMercuryConjunctionStrict(jde)
return mercuryRetrograde(nextConjunction + 2)
motion := mercuryRADerivative(jde, mercuryStationDerivativeStepDay)
if motion > mercuryStationMotionTolerance {
p2r := NextMercuryProgradeToRetrograde(jde)
if !mercuryStationBetween(jde, TD2UT(p2r, true)) {
return p2r
}
r2p := NextMercuryRetrogradeToPrograde(jde)
if p2r < r2p {
return p2r
}
return r2p
}
return date
if motion < -mercuryStationMotionTolerance {
r2p := NextMercuryRetrogradeToPrograde(jde)
if !mercuryStationBetween(jde, TD2UT(r2p, true)) {
return r2p
}
p2r := NextMercuryProgradeToRetrograde(jde)
if p2r < r2p {
return p2r
}
return r2p
}
return nextMercuryRetrogradeFromTyped(jde)
}
func lastMercuryRetrogradeFromTyped(jde float64) float64 {
p2r := LastMercuryProgradeToRetrograde(jde)
r2p := LastMercuryRetrogradeToPrograde(jde)
if p2r > r2p {
return p2r
}
return r2p
}
func LastMercuryRetrograde(jde float64) float64 {
lastConjunction := LastMercuryConjunctionStrict(jde)
date := mercuryRetrograde(lastConjunction + 2)
if !eventUTQueryBeforeOrEqual(date, jde) {
previousConjunction := LastMercuryConjunctionStrict(eventUTLastQueryTT(lastConjunction))
return mercuryRetrograde(previousConjunction + 2)
motion := mercuryRADerivative(jde, mercuryStationDerivativeStepDay)
if motion > mercuryStationMotionTolerance {
r2p := LastMercuryRetrogradeToPrograde(jde)
if !mercuryStationBetween(TD2UT(r2p, true), jde) {
return r2p
}
p2r := LastMercuryProgradeToRetrograde(jde)
if p2r > r2p {
return p2r
}
return r2p
}
return date
if motion < -mercuryStationMotionTolerance {
p2r := LastMercuryProgradeToRetrograde(jde)
if !mercuryStationBetween(TD2UT(p2r, true), jde) {
return p2r
}
r2p := LastMercuryRetrogradeToPrograde(jde)
if p2r > r2p {
return p2r
}
return r2p
}
return lastMercuryRetrogradeFromTyped(jde)
}
func LastMercuryRetrogradeStrict(jde float64) float64 {
@@ -389,22 +444,6 @@ func NextMercuryRetrogradeStrict(jde float64) float64 {
return NextMercuryRetrograde(jde)
}
func NextMercuryProgradeToRetrograde(jde float64) float64 {
return nextMercuryTypedStation(jde, true)
}
func NextMercuryRetrogradeToPrograde(jde float64) float64 {
return nextMercuryTypedStation(jde, false)
}
func LastMercuryProgradeToRetrograde(jde float64) float64 {
return lastMercuryTypedStation(jde, true)
}
func LastMercuryRetrogradeToPrograde(jde float64) float64 {
return lastMercuryTypedStation(jde, false)
}
func MercurySunElongation(jde float64) float64 {
lo1, bo1 := MercuryApparentLoBo(jde)
lo2 := HSunApparentLo(jde)
@@ -466,52 +505,77 @@ func mercuryWestElongationWindowContaining(jde float64) (float64, float64) {
}
func nextMercuryGreatestElongationTyped(jde float64, east bool) float64 {
if !isFiniteFloat(jde) {
return math.NaN()
}
if east {
start, windowEnd := mercuryEastElongationWindowContaining(jde)
for {
for i := 0; i < eventDirectionalSearchIterations; i++ {
date := mercuryGreatestElongationInWindow(start, windowEnd)
if !isFiniteFloat(date) {
return math.NaN()
}
if eventUTQueryAfterOrEqual(date, jde) {
return date
}
nextInferior := NextMercuryInferiorConjunction(eventUTNextQueryTT(windowEnd))
start, windowEnd = mercuryEastElongationWindowEndingAt(nextInferior)
}
return math.NaN()
}
start, windowEnd := mercuryWestElongationWindowContaining(jde)
for {
for i := 0; i < eventDirectionalSearchIterations; i++ {
date := mercuryGreatestElongationInWindow(start, windowEnd)
if !isFiniteFloat(date) {
return math.NaN()
}
if eventUTQueryAfterOrEqual(date, jde) {
return date
}
nextSuperior := NextMercurySuperiorConjunction(eventUTNextQueryTT(windowEnd))
start, windowEnd = mercuryWestElongationWindowEndingAt(nextSuperior)
}
return math.NaN()
}
func lastMercuryGreatestElongationTyped(jde float64, east bool) float64 {
if !isFiniteFloat(jde) {
return math.NaN()
}
if east {
start, windowEnd := mercuryEastElongationWindowContaining(jde)
for {
for i := 0; i < eventDirectionalSearchIterations; i++ {
date := mercuryGreatestElongationInWindow(start, windowEnd)
if !isFiniteFloat(date) {
return math.NaN()
}
if eventUTQueryBeforeOrEqual(date, jde) {
return date
}
prevInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(start))
start, windowEnd = mercuryEastElongationWindowEndingAt(prevInferior)
}
return math.NaN()
}
start, windowEnd := mercuryWestElongationWindowContaining(jde)
for {
for i := 0; i < eventDirectionalSearchIterations; i++ {
date := mercuryGreatestElongationInWindow(start, windowEnd)
if !isFiniteFloat(date) {
return math.NaN()
}
if eventUTQueryBeforeOrEqual(date, jde) {
return date
}
prevSuperior := LastMercurySuperiorConjunction(eventUTLastQueryTT(start))
start, windowEnd = mercuryWestElongationWindowEndingAt(prevSuperior)
}
return math.NaN()
}
func mercuryGreatestElongation(jde float64) float64 {
if !isFiniteFloat(jde) {
return math.NaN()
}
solarRADelta := func(jde float64) float64 {
sub := Limit360(MercuryApparentRa(jde) - SunApparentRa(jde))
if sub > 180 {
@@ -540,23 +604,31 @@ func mercuryGreatestElongation(jde float64) float64 {
} else {
jde = lastConjunction + ((nextConjunction - lastConjunction) / 6.0)
}
for {
found := false
for i := 0; i < eventDirectionalSearchIterations; i++ {
currentRate := elongationRate(jde, 1.0/86400.0)
if !isFiniteFloat(currentRate) {
return math.NaN()
}
if math.Abs(currentRate) > 0.4 {
jde += 2
continue
}
found = true
break
}
if !found {
return math.NaN()
}
estimateJD := jde
for {
prevJD := estimateJD
var ok bool
estimateJD, ok = eventNewtonRefine(estimateJD, 30.0/86400.0, func(prevJD float64) float64 {
rateValue := elongationRate(prevJD, 2.0/86400.0)
rateSlope := (elongationRate(prevJD+15.0/86400.0, 2.0/86400.0) - elongationRate(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
estimateJD = prevJD - rateValue/rateSlope
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
break
}
return rateValue / rateSlope
})
if !ok {
return math.NaN()
}
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
return elongationRate(jd, 0.5/86400.0)