feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
This commit is contained in:
+249
-181
@@ -17,6 +17,20 @@ const (
|
||||
mercuryStationCoarseStepDay = 2.0
|
||||
mercuryStationHalfWindowDay = 2.0
|
||||
mercuryStationMotionTolerance = 1e-3
|
||||
// mercuryStationAnchorAttempts 类型化「留」在锚点不满足侧向不变量时,最多推进/回退几个会合周期。
|
||||
mercuryStationAnchorAttempts = 4
|
||||
// mercuryConjunctionSameInstantDegrees 合搜索「同刻」快速路径的视黄经差触发阈值(度)。
|
||||
// 0.05 度约合 30 分钟的时间跨度,远大于截断级数在根附近的误差,
|
||||
// 从而保证「查询落在合附近」时一定走精确解 + 侧向判定,而不是被方向扫描跨过去。
|
||||
mercuryConjunctionSameInstantDegrees = 5.0e-2
|
||||
// mercuryConjunctionScanStepDay / mercuryConjunctionScanMaxSteps 方向性括号扫描参数:
|
||||
// 8 天一步 × 80 步 = 640 天,覆盖一个水星会合周期(115.88 天)且有大量余量。
|
||||
mercuryConjunctionScanStepDay = 8.0
|
||||
mercuryConjunctionScanMaxSteps = 80
|
||||
// mercuryConjunctionPolishToleranceDay 全项抛光的时间容差(0.01 秒):
|
||||
// 割线法从 8 天括号收敛到该量级只需多 1~2 次求值,但把合时刻精度保持在毫秒级
|
||||
// (与旧实现牛顿法的 1e-5 天口径一致)。
|
||||
mercuryConjunctionPolishToleranceDay = 0.01 / 86400.0
|
||||
)
|
||||
|
||||
type mercuryConjunctionLBR struct {
|
||||
@@ -132,15 +146,25 @@ func mercuryConjunctionExactTT(seed float64, inferior bool) float64 {
|
||||
return estimateJD
|
||||
}
|
||||
|
||||
// mercuryConjunction 在 jde 的指定方向上求最近一次水星合(内合/外合)。
|
||||
//
|
||||
// 旧实现用「启发式跳 + 2 天一步走」:跳过头落到合之后时,|Δ| 会持续变大,
|
||||
// 于是走满一个会合周期、跳过一次合(例如 2008-01-22 查到 2008-06-07 而不是 2008-02-06),
|
||||
// 并且下游的类型化「留」会因此返回错事件、甚至让 Last 返回未来。
|
||||
// 新实现改成方向性括号扫描:截断级数逐段找异号区间(顺序扫描 ⇒ 一定取最近的一个合),
|
||||
// 再用全项级数在括号内抛光;扫满 640 天仍无括号时有界返回 NaN。
|
||||
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 {
|
||||
if math.Abs(mercuryConjunctionExactDelta(jde)) <= mercuryConjunctionSameInstantDegrees {
|
||||
best := math.NaN()
|
||||
consider := func(inferior bool) {
|
||||
eventUT := TD2UT(mercuryConjunctionExactTT(jde, inferior), false)
|
||||
if !isFiniteFloat(eventUT) {
|
||||
return
|
||||
}
|
||||
if next == 0 && !eventUTQueryBeforeOrEqual(eventUT, jde) {
|
||||
return
|
||||
}
|
||||
@@ -157,54 +181,87 @@ func mercuryConjunction(jde float64, next uint8) float64 {
|
||||
return best
|
||||
}
|
||||
}
|
||||
currentDelta := mercuryConjunctionExactDelta(jde)
|
||||
// pos 大于0:远离太阳 小于0:靠近太阳
|
||||
distanceTrend := math.Abs(mercuryConjunctionExactDelta(jde+1/86400.0)) - math.Abs(currentDelta)
|
||||
if distanceTrend >= 0 && next == 1 && currentDelta > 0 {
|
||||
jde += MERCURY_S_PERIOD/8.0 + 2
|
||||
|
||||
direction := 1.0
|
||||
if next == 0 {
|
||||
direction = -1
|
||||
}
|
||||
if distanceTrend >= 0 && next == 1 && currentDelta < 0 {
|
||||
jde += MERCURY_S_PERIOD/6.0 + 2
|
||||
leftJD := jde
|
||||
leftValue := mercuryConjunctionDeltaN(leftJD, mercuryEventSearchN)
|
||||
if !isFiniteFloat(leftValue) {
|
||||
return math.NaN()
|
||||
}
|
||||
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
|
||||
}
|
||||
found := false
|
||||
for i := 0; i < eventDirectionalSearchIterations; i++ {
|
||||
currentDelta := mercuryConjunctionExactDelta(jde)
|
||||
nextDelta := mercuryConjunctionExactDelta(jde + 1/86400.0)
|
||||
if !isFiniteFloat(currentDelta) || !isFiniteFloat(nextDelta) {
|
||||
for i := 0; i < mercuryConjunctionScanMaxSteps; i++ {
|
||||
rightJD := jde + direction*mercuryConjunctionScanStepDay*float64(i+1)
|
||||
rightValue := mercuryConjunctionDeltaN(rightJD, mercuryEventSearchN)
|
||||
if !isFiniteFloat(rightValue) {
|
||||
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
|
||||
} else {
|
||||
jde -= 2
|
||||
if leftValue == 0 || rightValue == 0 || leftValue*rightValue < 0 {
|
||||
return mercuryConjunctionPolish(jde, leftJD, rightJD, direction)
|
||||
}
|
||||
leftJD, leftValue = rightJD, rightValue
|
||||
}
|
||||
return math.NaN()
|
||||
}
|
||||
|
||||
// mercuryConjunctionDeltaN 截断级数下的水星-太阳视黄经差(度,[-180,180]),用于方向性括号扫描。
|
||||
func mercuryConjunctionDeltaN(jd float64, n int) float64 {
|
||||
return mercuryConjunctionAngleDelta(MercuryApparentLoN(jd, n) - HSunApparentLoN(jd, n))
|
||||
}
|
||||
|
||||
// mercuryConjunctionPolish 用全项级数在截断级数给出的括号内抛光。
|
||||
// 截断误差可能让括号两端在全项函数上同号(罕见),此时沿扫描方向再扩一两个扫描步;
|
||||
// 若仍未被确认(典型情形:查询几乎正好落在合上,截断级数在根两侧的符号与全项不一致),
|
||||
// 退回全项级数的方向扫描,保证有界且不返回 NaN。
|
||||
func mercuryConjunctionPolish(jde, leftJD, rightJD, direction float64) float64 {
|
||||
for attempt := 0; attempt < 3; attempt++ {
|
||||
leftValue := mercuryConjunctionExactDelta(leftJD)
|
||||
rightValue := mercuryConjunctionExactDelta(rightJD)
|
||||
if !isFiniteFloat(leftValue) || !isFiniteFloat(rightValue) {
|
||||
return math.NaN()
|
||||
}
|
||||
if leftValue == 0 || rightValue == 0 || leftValue*rightValue < 0 {
|
||||
root, ok := eventBracketSecantRoot(leftJD, rightJD, leftValue, rightValue,
|
||||
mercuryConjunctionPolishToleranceDay, mercuryConjunctionExactDelta)
|
||||
if !ok {
|
||||
return math.NaN()
|
||||
}
|
||||
return TD2UT(root, false)
|
||||
}
|
||||
if direction > 0 {
|
||||
rightJD += mercuryConjunctionScanStepDay
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
if !found {
|
||||
return math.NaN()
|
||||
leftJD -= mercuryConjunctionScanStepDay
|
||||
}
|
||||
return mercuryConjunctionFullDirectionalScan(jde, direction)
|
||||
}
|
||||
|
||||
inferior := mercuryConjunctionExactTT(jde, true)
|
||||
superior := mercuryConjunctionExactTT(jde, false)
|
||||
if !isFiniteFloat(inferior) || !isFiniteFloat(superior) {
|
||||
// mercuryConjunctionFullDirectionalScan 全项级数的方向扫描(截断括号未被确认时的兜底)。
|
||||
func mercuryConjunctionFullDirectionalScan(jde, direction float64) float64 {
|
||||
leftJD := jde
|
||||
leftValue := mercuryConjunctionExactDelta(leftJD)
|
||||
if !isFiniteFloat(leftValue) {
|
||||
return math.NaN()
|
||||
}
|
||||
best := inferior
|
||||
if math.Abs(superior-jde) < math.Abs(inferior-jde) {
|
||||
best = superior
|
||||
for i := 0; i < mercuryConjunctionScanMaxSteps; i++ {
|
||||
rightJD := jde + direction*mercuryConjunctionScanStepDay*float64(i+1)
|
||||
rightValue := mercuryConjunctionExactDelta(rightJD)
|
||||
if !isFiniteFloat(rightValue) {
|
||||
return math.NaN()
|
||||
}
|
||||
if leftValue == 0 || rightValue == 0 || leftValue*rightValue < 0 {
|
||||
root, ok := eventBracketSecantRoot(leftJD, rightJD, leftValue, rightValue,
|
||||
mercuryConjunctionPolishToleranceDay, mercuryConjunctionExactDelta)
|
||||
if !ok {
|
||||
return math.NaN()
|
||||
}
|
||||
return TD2UT(root, false)
|
||||
}
|
||||
leftJD, leftValue = rightJD, rightValue
|
||||
}
|
||||
return TD2UT(best, false)
|
||||
return math.NaN()
|
||||
}
|
||||
|
||||
func LastMercuryConjunction(jde float64) float64 {
|
||||
@@ -293,8 +350,13 @@ func mercuryStationBetween(startTT, endTT float64) bool {
|
||||
if endTT-startTT <= 0 {
|
||||
return false
|
||||
}
|
||||
// 跨度超过一个回留窗口时截断扫描已不可靠:返回 false 不再阻断快速路径。
|
||||
// 此时候选事件由 mercuryConjunction 的完整方向扫描保证,快速路径无需再复核。
|
||||
// A span beyond the station window makes the truncated scan unreliable; report false so the
|
||||
// fast path is not blocked. The typed candidate is already guaranteed by mercuryConjunction's
|
||||
// exhaustive directional scan.
|
||||
if endTT-startTT > mercuryStationWindowDays {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
// 截断扫描足以判断单候选快速路径是否安全 / A truncated scan is enough to decide whether the one-candidate fast path is safe.
|
||||
left := startTT
|
||||
@@ -324,114 +386,161 @@ func mercuryRetrogradeToProgradeAroundInferior(inferiorUT float64) float64 {
|
||||
return mercuryStationInWindow(inferiorTT, inferiorTT+mercuryStationWindowDays)
|
||||
}
|
||||
|
||||
// 类型化「留」统一结构:以下合为锚点求候选,再用侧向不变量(Next 不得早于查询、
|
||||
// Last 不得晚于查询)校验;不满足则把锚点推进/回退一个会合周期重试,最多
|
||||
// mercuryStationAnchorAttempts 次,最终仍有界失败为 NaN。
|
||||
// 这保证 Last* 永远不会返回未来、Next* 永远不会返回过去的事件。
|
||||
func NextMercuryProgradeToRetrograde(jde float64) float64 {
|
||||
inferior := NextMercuryInferiorConjunction(jde)
|
||||
date := mercuryProgradeToRetrogradeAroundInferior(inferior)
|
||||
if eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
if !isFiniteFloat(jde) {
|
||||
return math.NaN()
|
||||
}
|
||||
followingInferior := NextMercuryInferiorConjunction(eventUTNextQueryTT(inferior))
|
||||
return mercuryProgradeToRetrogradeAroundInferior(followingInferior)
|
||||
inferior := NextMercuryInferiorConjunction(jde)
|
||||
for i := 0; i < mercuryStationAnchorAttempts && isFiniteFloat(inferior); i++ {
|
||||
date := mercuryProgradeToRetrogradeAroundInferior(inferior)
|
||||
if isFiniteFloat(date) && stationUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
inferior = NextMercuryInferiorConjunction(eventUTNextQueryTT(inferior))
|
||||
}
|
||||
return math.NaN()
|
||||
}
|
||||
|
||||
func NextMercuryRetrogradeToPrograde(jde float64) float64 {
|
||||
inferior := LastMercuryInferiorConjunction(jde)
|
||||
date := mercuryRetrogradeToProgradeAroundInferior(inferior)
|
||||
if eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
if !isFiniteFloat(jde) {
|
||||
return math.NaN()
|
||||
}
|
||||
nextInferior := NextMercuryInferiorConjunction(eventUTNextQueryTT(inferior))
|
||||
return mercuryRetrogradeToProgradeAroundInferior(nextInferior)
|
||||
inferior := LastMercuryInferiorConjunction(jde)
|
||||
for i := 0; i < mercuryStationAnchorAttempts && isFiniteFloat(inferior); i++ {
|
||||
date := mercuryRetrogradeToProgradeAroundInferior(inferior)
|
||||
if isFiniteFloat(date) && stationUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
inferior = NextMercuryInferiorConjunction(eventUTNextQueryTT(inferior))
|
||||
}
|
||||
return math.NaN()
|
||||
}
|
||||
|
||||
func LastMercuryProgradeToRetrograde(jde float64) float64 {
|
||||
inferior := NextMercuryInferiorConjunction(jde)
|
||||
date := mercuryProgradeToRetrogradeAroundInferior(inferior)
|
||||
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
if !isFiniteFloat(jde) {
|
||||
return math.NaN()
|
||||
}
|
||||
previousInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(inferior))
|
||||
return mercuryProgradeToRetrogradeAroundInferior(previousInferior)
|
||||
inferior := NextMercuryInferiorConjunction(jde)
|
||||
for i := 0; i < mercuryStationAnchorAttempts && isFiniteFloat(inferior); i++ {
|
||||
date := mercuryProgradeToRetrogradeAroundInferior(inferior)
|
||||
if isFiniteFloat(date) && stationUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
inferior = LastMercuryInferiorConjunction(eventUTLastQueryTT(inferior))
|
||||
}
|
||||
return math.NaN()
|
||||
}
|
||||
|
||||
func LastMercuryRetrogradeToPrograde(jde float64) float64 {
|
||||
inferior := LastMercuryInferiorConjunction(jde)
|
||||
date := mercuryRetrogradeToProgradeAroundInferior(inferior)
|
||||
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
if !isFiniteFloat(jde) {
|
||||
return math.NaN()
|
||||
}
|
||||
previousInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(inferior))
|
||||
return mercuryRetrogradeToProgradeAroundInferior(previousInferior)
|
||||
inferior := LastMercuryInferiorConjunction(jde)
|
||||
for i := 0; i < mercuryStationAnchorAttempts && isFiniteFloat(inferior); i++ {
|
||||
date := mercuryRetrogradeToProgradeAroundInferior(inferior)
|
||||
if isFiniteFloat(date) && stationUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
inferior = LastMercuryInferiorConjunction(eventUTLastQueryTT(inferior))
|
||||
}
|
||||
return math.NaN()
|
||||
}
|
||||
|
||||
// earliestFiniteEventUT / latestFiniteEventUT 在候选中取最早/最晚的有限值(全部非有限时返回 NaN)。
|
||||
func earliestFiniteEventUT(candidates ...float64) float64 {
|
||||
best := math.NaN()
|
||||
for _, candidate := range candidates {
|
||||
if !isFiniteFloat(candidate) {
|
||||
continue
|
||||
}
|
||||
if math.IsNaN(best) || candidate < best {
|
||||
best = candidate
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
func latestFiniteEventUT(candidates ...float64) float64 {
|
||||
best := math.NaN()
|
||||
for _, candidate := range candidates {
|
||||
if !isFiniteFloat(candidate) {
|
||||
continue
|
||||
}
|
||||
if math.IsNaN(best) || candidate > best {
|
||||
best = candidate
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
func nextMercuryRetrogradeFromTyped(jde float64) float64 {
|
||||
p2r := NextMercuryProgradeToRetrograde(jde)
|
||||
r2p := NextMercuryRetrogradeToPrograde(jde)
|
||||
if p2r < r2p {
|
||||
return p2r
|
||||
}
|
||||
return r2p
|
||||
return earliestFiniteEventUT(NextMercuryProgradeToRetrograde(jde), NextMercuryRetrogradeToPrograde(jde))
|
||||
}
|
||||
|
||||
func NextMercuryRetrograde(jde float64) float64 {
|
||||
if !isFiniteFloat(jde) {
|
||||
return math.NaN()
|
||||
}
|
||||
motion := mercuryRADerivative(jde, mercuryStationDerivativeStepDay)
|
||||
if motion > mercuryStationMotionTolerance {
|
||||
p2r := NextMercuryProgradeToRetrograde(jde)
|
||||
if !mercuryStationBetween(jde, TD2UT(p2r, true)) {
|
||||
if isFiniteFloat(p2r) && !mercuryStationBetween(jde, TD2UT(p2r, true)) {
|
||||
return p2r
|
||||
}
|
||||
r2p := NextMercuryRetrogradeToPrograde(jde)
|
||||
if p2r < r2p {
|
||||
return p2r
|
||||
best := earliestFiniteEventUT(p2r, NextMercuryRetrogradeToPrograde(jde))
|
||||
if !stationUTQueryAfterOrEqual(best, jde) {
|
||||
return math.NaN()
|
||||
}
|
||||
return r2p
|
||||
return best
|
||||
}
|
||||
if motion < -mercuryStationMotionTolerance {
|
||||
r2p := NextMercuryRetrogradeToPrograde(jde)
|
||||
if !mercuryStationBetween(jde, TD2UT(r2p, true)) {
|
||||
if isFiniteFloat(r2p) && !mercuryStationBetween(jde, TD2UT(r2p, true)) {
|
||||
return r2p
|
||||
}
|
||||
p2r := NextMercuryProgradeToRetrograde(jde)
|
||||
if p2r < r2p {
|
||||
return p2r
|
||||
best := earliestFiniteEventUT(NextMercuryProgradeToRetrograde(jde), r2p)
|
||||
if !stationUTQueryAfterOrEqual(best, jde) {
|
||||
return math.NaN()
|
||||
}
|
||||
return r2p
|
||||
return best
|
||||
}
|
||||
return nextMercuryRetrogradeFromTyped(jde)
|
||||
}
|
||||
|
||||
func lastMercuryRetrogradeFromTyped(jde float64) float64 {
|
||||
p2r := LastMercuryProgradeToRetrograde(jde)
|
||||
r2p := LastMercuryRetrogradeToPrograde(jde)
|
||||
if p2r > r2p {
|
||||
return p2r
|
||||
}
|
||||
return r2p
|
||||
return latestFiniteEventUT(LastMercuryProgradeToRetrograde(jde), LastMercuryRetrogradeToPrograde(jde))
|
||||
}
|
||||
|
||||
func LastMercuryRetrograde(jde float64) float64 {
|
||||
if !isFiniteFloat(jde) {
|
||||
return math.NaN()
|
||||
}
|
||||
motion := mercuryRADerivative(jde, mercuryStationDerivativeStepDay)
|
||||
if motion > mercuryStationMotionTolerance {
|
||||
r2p := LastMercuryRetrogradeToPrograde(jde)
|
||||
if !mercuryStationBetween(TD2UT(r2p, true), jde) {
|
||||
if isFiniteFloat(r2p) && !mercuryStationBetween(TD2UT(r2p, true), jde) {
|
||||
return r2p
|
||||
}
|
||||
p2r := LastMercuryProgradeToRetrograde(jde)
|
||||
if p2r > r2p {
|
||||
return p2r
|
||||
best := latestFiniteEventUT(LastMercuryProgradeToRetrograde(jde), r2p)
|
||||
if !stationUTQueryBeforeOrEqual(best, jde) {
|
||||
return math.NaN()
|
||||
}
|
||||
return r2p
|
||||
return best
|
||||
}
|
||||
if motion < -mercuryStationMotionTolerance {
|
||||
p2r := LastMercuryProgradeToRetrograde(jde)
|
||||
if !mercuryStationBetween(TD2UT(p2r, true), jde) {
|
||||
if isFiniteFloat(p2r) && !mercuryStationBetween(TD2UT(p2r, true), jde) {
|
||||
return p2r
|
||||
}
|
||||
r2p := LastMercuryRetrogradeToPrograde(jde)
|
||||
if p2r > r2p {
|
||||
return p2r
|
||||
best := latestFiniteEventUT(p2r, LastMercuryRetrogradeToPrograde(jde))
|
||||
if !stationUTQueryBeforeOrEqual(best, jde) {
|
||||
return math.NaN()
|
||||
}
|
||||
return r2p
|
||||
return best
|
||||
}
|
||||
return lastMercuryRetrogradeFromTyped(jde)
|
||||
}
|
||||
@@ -458,20 +567,15 @@ func mercurySunElongationN(jde float64, n int) float64 {
|
||||
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
||||
}
|
||||
|
||||
func mercuryTrueElongationN(jde float64, n int) float64 {
|
||||
earth := mercuryHelioN(-1, jde, n)
|
||||
planetPos := mercuryHelioN(1, jde, n)
|
||||
geo := mercuryGeocentric(planetPos, earth)
|
||||
return StarAngularSeparation(geo.lo, geo.bo, HSunTrueLoN(jde, n), HSunTrueBoN(jde, n))
|
||||
}
|
||||
|
||||
// mercuryGreatestElongationInWindow 求窗口内大距:目标是公开 MercurySunElongation 的极大(视距角),
|
||||
// 而不是忽略光行差/视位置修正的真距角,否则返回的时刻不是调用方能量到的那个极值。
|
||||
// 窗口两端是世界时,目标函数收力学时,因此逐次换算。
|
||||
func mercuryGreatestElongationInWindow(start, end float64) float64 {
|
||||
best := maximizeInWindow(start, end, 2.0, func(jd float64) float64 {
|
||||
return mercuryTrueElongationN(jd, mercuryEventSearchN)
|
||||
}, func(jd float64) float64 {
|
||||
return mercuryTrueElongationN(jd, -1)
|
||||
return maximizeInWindow(start, end, 2.0, func(utJD float64) float64 {
|
||||
return mercurySunElongationN(TD2UT(utJD, true), mercuryEventSearchN)
|
||||
}, func(utJD float64) float64 {
|
||||
return MercurySunElongation(TD2UT(utJD, true))
|
||||
})
|
||||
return TD2UT(best, false)
|
||||
}
|
||||
|
||||
func mercuryEastElongationWindowEndingAt(inferior float64) (float64, float64) {
|
||||
@@ -572,93 +676,57 @@ func lastMercuryGreatestElongationTyped(jde float64, east bool) float64 {
|
||||
return math.NaN()
|
||||
}
|
||||
|
||||
func mercuryGreatestElongation(jde float64) float64 {
|
||||
// mercuryElongationWindowAt 返回包含 jde 的该侧大距窗口;查询落在合的 4 秒内边距里时两侧都不包含。
|
||||
func mercuryElongationWindowAt(jde float64, east bool) (float64, float64, bool) {
|
||||
if east {
|
||||
start, end := mercuryEastElongationWindowEndingAt(NextMercuryInferiorConjunction(jde))
|
||||
return start, end, eventUTQueryBeforeOrEqual(start, jde) && eventUTQueryAfterOrEqual(end, jde)
|
||||
}
|
||||
start, end := mercuryWestElongationWindowEndingAt(NextMercurySuperiorConjunction(jde))
|
||||
return start, end, eventUTQueryBeforeOrEqual(start, jde) && eventUTQueryAfterOrEqual(end, jde)
|
||||
}
|
||||
|
||||
// 无东西侧参数的 Next/Last 先只看查询所在窗口那一侧:该侧大距未过就是答案,已过则另一侧紧接着的
|
||||
// 下一个才是答案,因此只有在查询贴住合(两侧窗口都不含)时才退化为两侧都算。赤经差在合附近会提前
|
||||
// 变号,不能用它定窗口。
|
||||
func NextMercuryGreatestElongation(jde float64) float64 {
|
||||
if !isFiniteFloat(jde) {
|
||||
return math.NaN()
|
||||
}
|
||||
solarRADelta := func(jde float64) float64 {
|
||||
sub := Limit360(MercuryApparentRa(jde) - SunApparentRa(jde))
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub
|
||||
}
|
||||
elongationRate := func(jde float64, delta float64) float64 {
|
||||
sub := MercurySunElongation(jde+delta) - MercurySunElongation(jde-delta)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
lastConjunction := LastMercuryConjunctionStrict(jde)
|
||||
nextConjunction := NextMercuryConjunctionStrict(jde)
|
||||
currentRADelta := solarRADelta(jde)
|
||||
if currentRADelta > 0 {
|
||||
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.0 * 2.0)
|
||||
} else {
|
||||
jde = lastConjunction + ((nextConjunction - lastConjunction) / 6.0)
|
||||
}
|
||||
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
|
||||
for _, east := range [2]bool{true, false} {
|
||||
start, end, ok := mercuryElongationWindowAt(jde, east)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
if date := mercuryGreatestElongationInWindow(start, end); isFiniteFloat(date) && eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
if date := nextMercuryGreatestElongationTyped(jde, !east); isFiniteFloat(date) && eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
break
|
||||
}
|
||||
if !found {
|
||||
return math.NaN()
|
||||
}
|
||||
estimateJD := jde
|
||||
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)
|
||||
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)
|
||||
})
|
||||
//fmt.Println((bestJD - lastConjunction) / (nextConjunction - lastConjunction))
|
||||
return TD2UT(bestJD, false)
|
||||
}
|
||||
|
||||
func NextMercuryGreatestElongation(jde float64) float64 {
|
||||
east := NextMercuryGreatestElongationEast(jde)
|
||||
west := NextMercuryGreatestElongationWest(jde)
|
||||
if sameEventJD(east, west) {
|
||||
return east
|
||||
}
|
||||
if east < west {
|
||||
return east
|
||||
}
|
||||
return west
|
||||
return earliestFiniteEventUT(nextMercuryGreatestElongationTyped(jde, true), nextMercuryGreatestElongationTyped(jde, false))
|
||||
}
|
||||
|
||||
func LastMercuryGreatestElongation(jde float64) float64 {
|
||||
east := LastMercuryGreatestElongationEast(jde)
|
||||
west := LastMercuryGreatestElongationWest(jde)
|
||||
if sameEventJD(east, west) {
|
||||
return east
|
||||
if !isFiniteFloat(jde) {
|
||||
return math.NaN()
|
||||
}
|
||||
if east > west {
|
||||
return east
|
||||
for _, east := range [2]bool{true, false} {
|
||||
start, end, ok := mercuryElongationWindowAt(jde, east)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if date := mercuryGreatestElongationInWindow(start, end); isFiniteFloat(date) && eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
if date := lastMercuryGreatestElongationTyped(jde, !east); isFiniteFloat(date) && eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
break
|
||||
}
|
||||
return west
|
||||
return latestFiniteEventUT(lastMercuryGreatestElongationTyped(jde, true), lastMercuryGreatestElongationTyped(jde, false))
|
||||
}
|
||||
|
||||
func LastMercuryInferiorConjunctionInclusive(jde float64) float64 {
|
||||
|
||||
Reference in New Issue
Block a user