044f2f4381
- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算 - 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询 - 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出 - 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解 - 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤 - 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持 - 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理 - 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
416 lines
13 KiB
Go
416 lines
13 KiB
Go
package basic
|
|
|
|
import (
|
|
"math"
|
|
|
|
"b612.me/astro/internal/geodata"
|
|
)
|
|
|
|
const (
|
|
moonPlanetConjunctionEstimateN = 8
|
|
moonPlanetConjunctionNearQueryDeltaDeg = 3.0
|
|
moonPlanetConjunctionDirectionEpsilon = 0.1 / 86400.0
|
|
moonPlanetConjunctionBracketStepDays = 0.5
|
|
moonPlanetConjunctionNearQueryStepDays = 0.25
|
|
moonPlanetConjunctionNearQueryHalfSpan = 1.5
|
|
moonPlanetConjunctionBracketHalfSpan = 2.0
|
|
moonPlanetConjunctionBracketGrowth = 2.0
|
|
moonPlanetConjunctionBracketAttempts = 3
|
|
moonPlanetConjunctionRefineStepDays = 0.5 / 86400.0
|
|
moonPlanetConjunctionEventTolerance = 0.01
|
|
moonPlanetConjunctionFallbackSpanScale = 1.5
|
|
)
|
|
|
|
type moonPlanetConjunctionLocalResult struct {
|
|
lastUT float64
|
|
nextUT float64
|
|
}
|
|
|
|
func emptyMoonPlanetConjunctionLocalResult() moonPlanetConjunctionLocalResult {
|
|
return moonPlanetConjunctionLocalResult{
|
|
lastUT: math.NaN(),
|
|
nextUT: math.NaN(),
|
|
}
|
|
}
|
|
|
|
// MoonPlanetConjunctionPlanet 月球合月目标行星 / target planet for Moon-planet conjunction events.
|
|
type MoonPlanetConjunctionPlanet int
|
|
|
|
const (
|
|
MoonPlanetConjunctionMercury MoonPlanetConjunctionPlanet = iota + 1
|
|
MoonPlanetConjunctionVenus
|
|
MoonPlanetConjunctionMars
|
|
MoonPlanetConjunctionJupiter
|
|
MoonPlanetConjunctionSaturn
|
|
MoonPlanetConjunctionUranus
|
|
MoonPlanetConjunctionNeptune
|
|
)
|
|
|
|
func moonPlanetConjunctionDeltaAt(jdTT float64, planet MoonPlanetConjunctionPlanet, n int, instances ...*Instance) float64 {
|
|
inst := instanceValue(instances)
|
|
|
|
moonRA := HMoonGeocentricApparentRaNWithInstance(jdTT, n, inst)
|
|
var planetRA float64
|
|
switch planet {
|
|
case MoonPlanetConjunctionMercury:
|
|
planetRA = MercuryApparentRaNWithInstance(jdTT, n, inst)
|
|
case MoonPlanetConjunctionVenus:
|
|
planetRA = VenusApparentRaNWithInstance(jdTT, n, inst)
|
|
case MoonPlanetConjunctionMars:
|
|
planetRA = MarsApparentRaNWithInstance(jdTT, n, inst)
|
|
case MoonPlanetConjunctionJupiter:
|
|
planetRA = JupiterApparentRaNWithInstance(jdTT, n, inst)
|
|
case MoonPlanetConjunctionSaturn:
|
|
planetRA = SaturnApparentRaNWithInstance(jdTT, n, inst)
|
|
case MoonPlanetConjunctionUranus:
|
|
planetRA = UranusApparentRaNWithInstance(jdTT, n, inst)
|
|
case MoonPlanetConjunctionNeptune:
|
|
planetRA = NeptuneApparentRaNWithInstance(jdTT, n, inst)
|
|
default:
|
|
return math.NaN()
|
|
}
|
|
return geodata.NormalizeLongitude(moonRA - planetRA)
|
|
}
|
|
|
|
func moonPlanetConjunctionPeriodDays(planet MoonPlanetConjunctionPlanet) float64 {
|
|
switch planet {
|
|
case MoonPlanetConjunctionMercury:
|
|
return 28.1
|
|
case MoonPlanetConjunctionVenus:
|
|
return 28.4
|
|
case MoonPlanetConjunctionMars:
|
|
return 29.2
|
|
case MoonPlanetConjunctionJupiter:
|
|
return 28.0
|
|
case MoonPlanetConjunctionSaturn:
|
|
return 27.4
|
|
case MoonPlanetConjunctionUranus:
|
|
return 27.3
|
|
case MoonPlanetConjunctionNeptune:
|
|
return 27.3
|
|
default:
|
|
return math.NaN()
|
|
}
|
|
}
|
|
|
|
func moonPlanetConjunctionBeforeOrEqual(eventUT, queryTT float64) bool {
|
|
return eventUTQueryTTDelta(eventUT, queryTT) <= moonPlanetConjunctionDirectionEpsilon
|
|
}
|
|
|
|
func moonPlanetConjunctionAfterOrEqual(eventUT, queryTT float64) bool {
|
|
return eventUTQueryTTDelta(eventUT, queryTT) >= -moonPlanetConjunctionDirectionEpsilon
|
|
}
|
|
|
|
func moonPlanetConjunctionInDirection(eventUT, queryTT float64, direction int) bool {
|
|
switch direction {
|
|
case -1:
|
|
return moonPlanetConjunctionBeforeOrEqual(eventUT, queryTT)
|
|
case 1:
|
|
return moonPlanetConjunctionAfterOrEqual(eventUT, queryTT)
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func moonPlanetConjunctionFindBracket(centerTT, halfSpan, step float64, planet MoonPlanetConjunctionPlanet, instances ...*Instance) (float64, float64, bool) {
|
|
inst := instanceValue(instances)
|
|
|
|
if math.IsNaN(centerTT) || math.IsNaN(halfSpan) || math.IsNaN(step) || halfSpan <= 0 || step <= 0 {
|
|
return 0, 0, false
|
|
}
|
|
start := centerTT - halfSpan
|
|
end := centerTT + halfSpan
|
|
samples := int(math.Ceil((end-start)/step)) + 1
|
|
prevTT := start
|
|
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1, inst)
|
|
if math.IsNaN(prevVal) {
|
|
return 0, 0, false
|
|
}
|
|
if prevVal == 0 {
|
|
return prevTT, prevTT, true
|
|
}
|
|
bestLeft := 0.0
|
|
bestRight := 0.0
|
|
bestDistance := math.Inf(1)
|
|
for i := 1; i <= samples; i++ {
|
|
tt := start + float64(i)*step
|
|
if tt > end {
|
|
tt = end
|
|
}
|
|
val := moonPlanetConjunctionDeltaAt(tt, planet, -1, inst)
|
|
if math.IsNaN(val) {
|
|
return 0, 0, false
|
|
}
|
|
if val == 0 {
|
|
return tt, tt, true
|
|
}
|
|
if prevVal*val < 0 {
|
|
mid := (prevTT + tt) / 2.0
|
|
distance := math.Abs(mid - centerTT)
|
|
if distance < bestDistance {
|
|
bestLeft = prevTT
|
|
bestRight = tt
|
|
bestDistance = distance
|
|
}
|
|
}
|
|
if tt == end {
|
|
break
|
|
}
|
|
prevTT = tt
|
|
prevVal = val
|
|
}
|
|
if math.IsInf(bestDistance, 1) {
|
|
return 0, 0, false
|
|
}
|
|
return bestLeft, bestRight, true
|
|
}
|
|
|
|
func moonPlanetConjunctionRefineBracket(leftTT, rightTT float64, planet MoonPlanetConjunctionPlanet, instances ...*Instance) float64 {
|
|
inst := instanceValue(instances)
|
|
|
|
if leftTT > rightTT {
|
|
leftTT, rightTT = rightTT, leftTT
|
|
}
|
|
if leftTT == rightTT {
|
|
return leftTT
|
|
}
|
|
center := (leftTT + rightTT) / 2.0
|
|
halfWindow := (rightTT - leftTT) / 2.0
|
|
return eventZeroRefine(center, halfWindow, moonPlanetConjunctionRefineStepDays, func(sampleTT float64) float64 {
|
|
return moonPlanetConjunctionDeltaAt(sampleTT, planet, -1, inst)
|
|
})
|
|
}
|
|
|
|
func moonPlanetConjunctionEventUT(leftTT, rightTT float64, planet MoonPlanetConjunctionPlanet, instances ...*Instance) float64 {
|
|
inst := instanceValue(instances)
|
|
|
|
eventTT := moonPlanetConjunctionRefineBracket(leftTT, rightTT, planet, inst)
|
|
if math.Abs(moonPlanetConjunctionDeltaAt(eventTT, planet, -1, inst)) > moonPlanetConjunctionEventTolerance {
|
|
return math.NaN()
|
|
}
|
|
return TT2UTC(eventTT)
|
|
}
|
|
|
|
func moonPlanetConjunctionCollectLocalEvent(result *moonPlanetConjunctionLocalResult, queryTT, eventUT float64) {
|
|
if math.IsNaN(eventUT) {
|
|
return
|
|
}
|
|
if moonPlanetConjunctionBeforeOrEqual(eventUT, queryTT) {
|
|
if math.IsNaN(result.lastUT) || math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) < math.Abs(eventUTQueryTTDelta(result.lastUT, queryTT)) {
|
|
result.lastUT = eventUT
|
|
}
|
|
}
|
|
if moonPlanetConjunctionAfterOrEqual(eventUT, queryTT) {
|
|
if math.IsNaN(result.nextUT) || math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) < math.Abs(eventUTQueryTTDelta(result.nextUT, queryTT)) {
|
|
result.nextUT = eventUT
|
|
}
|
|
}
|
|
}
|
|
|
|
func moonPlanetConjunctionShouldCheckLocal(queryTT float64, planet MoonPlanetConjunctionPlanet, instances ...*Instance) bool {
|
|
inst := instanceValue(instances)
|
|
|
|
delta := moonPlanetConjunctionDeltaAt(queryTT, planet, moonPlanetConjunctionEstimateN, inst)
|
|
if math.IsNaN(delta) {
|
|
return false
|
|
}
|
|
return math.Abs(delta) <= moonPlanetConjunctionNearQueryDeltaDeg
|
|
}
|
|
|
|
func moonPlanetConjunctionLocalEvents(queryTT float64, planet MoonPlanetConjunctionPlanet, instances ...*Instance) moonPlanetConjunctionLocalResult {
|
|
inst := instanceValue(instances)
|
|
|
|
result := emptyMoonPlanetConjunctionLocalResult()
|
|
start := queryTT - moonPlanetConjunctionNearQueryHalfSpan
|
|
end := queryTT + moonPlanetConjunctionNearQueryHalfSpan
|
|
step := moonPlanetConjunctionNearQueryStepDays
|
|
prevTT := start
|
|
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1, inst)
|
|
if math.IsNaN(prevVal) {
|
|
return result
|
|
}
|
|
samples := int(math.Ceil((end-start)/step)) + 1
|
|
for i := 1; i <= samples; i++ {
|
|
tt := start + float64(i)*step
|
|
if tt > end {
|
|
tt = end
|
|
}
|
|
val := moonPlanetConjunctionDeltaAt(tt, planet, -1, inst)
|
|
if math.IsNaN(val) {
|
|
return emptyMoonPlanetConjunctionLocalResult()
|
|
}
|
|
if prevVal == 0 || val == 0 || prevVal*val < 0 {
|
|
moonPlanetConjunctionCollectLocalEvent(&result, queryTT, moonPlanetConjunctionEventUT(prevTT, tt, planet, inst))
|
|
}
|
|
if tt == end {
|
|
break
|
|
}
|
|
prevTT = tt
|
|
prevVal = val
|
|
}
|
|
return result
|
|
}
|
|
|
|
func moonPlanetConjunctionMaybeLocalEvents(queryTT float64, planet MoonPlanetConjunctionPlanet, instances ...*Instance) moonPlanetConjunctionLocalResult {
|
|
inst := instanceValue(instances)
|
|
|
|
if !moonPlanetConjunctionShouldCheckLocal(queryTT, planet, inst) {
|
|
return emptyMoonPlanetConjunctionLocalResult()
|
|
}
|
|
return moonPlanetConjunctionLocalEvents(queryTT, planet, inst)
|
|
}
|
|
|
|
func moonPlanetConjunctionGuessTT(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int, instances ...*Instance) float64 {
|
|
inst := instanceValue(instances)
|
|
|
|
delta := moonPlanetConjunctionDeltaAt(queryTT, planet, moonPlanetConjunctionEstimateN, inst)
|
|
if math.IsNaN(delta) {
|
|
return math.NaN()
|
|
}
|
|
period := moonPlanetConjunctionPeriodDays(planet)
|
|
if math.IsNaN(period) {
|
|
return math.NaN()
|
|
}
|
|
switch direction {
|
|
case -1:
|
|
return queryTT - innerLastCycleOffset(delta, period)
|
|
case 1:
|
|
return queryTT + innerNextCycleOffset(delta, period)
|
|
default:
|
|
return math.NaN()
|
|
}
|
|
}
|
|
|
|
func moonPlanetConjunctionDirectionalFallback(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int, instances ...*Instance) float64 {
|
|
inst := instanceValue(instances)
|
|
|
|
period := moonPlanetConjunctionPeriodDays(planet)
|
|
if math.IsNaN(period) {
|
|
return math.NaN()
|
|
}
|
|
span := period * moonPlanetConjunctionFallbackSpanScale
|
|
if span <= 0 {
|
|
return math.NaN()
|
|
}
|
|
step := moonPlanetConjunctionNearQueryStepDays
|
|
start := queryTT
|
|
end := queryTT
|
|
switch direction {
|
|
case -1:
|
|
start -= span
|
|
case 1:
|
|
end += span
|
|
default:
|
|
return math.NaN()
|
|
}
|
|
|
|
prevTT := start
|
|
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1, inst)
|
|
if math.IsNaN(prevVal) {
|
|
return math.NaN()
|
|
}
|
|
|
|
bestEventUT := math.NaN()
|
|
for tt := start + step; ; tt += step {
|
|
if tt > end {
|
|
tt = end
|
|
}
|
|
val := moonPlanetConjunctionDeltaAt(tt, planet, -1, inst)
|
|
if math.IsNaN(val) {
|
|
return math.NaN()
|
|
}
|
|
if prevVal == 0 || val == 0 || prevVal*val < 0 {
|
|
eventUT := moonPlanetConjunctionEventUT(prevTT, tt, planet, inst)
|
|
if !math.IsNaN(eventUT) && moonPlanetConjunctionInDirection(eventUT, queryTT, direction) {
|
|
if direction == 1 {
|
|
return eventUT
|
|
}
|
|
bestEventUT = eventUT
|
|
}
|
|
}
|
|
if tt == end {
|
|
break
|
|
}
|
|
prevTT = tt
|
|
prevVal = val
|
|
}
|
|
return bestEventUT
|
|
}
|
|
|
|
func moonPlanetConjunctionDirectionalEventWithLocal(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int, local moonPlanetConjunctionLocalResult, instances ...*Instance) float64 {
|
|
inst := instanceValue(instances)
|
|
|
|
switch direction {
|
|
case -1:
|
|
if !math.IsNaN(local.lastUT) {
|
|
return local.lastUT
|
|
}
|
|
case 1:
|
|
if !math.IsNaN(local.nextUT) {
|
|
return local.nextUT
|
|
}
|
|
}
|
|
guessTT := moonPlanetConjunctionGuessTT(queryTT, planet, direction, inst)
|
|
if math.IsNaN(guessTT) {
|
|
return math.NaN()
|
|
}
|
|
halfSpan := moonPlanetConjunctionBracketHalfSpan
|
|
for attempt := 0; attempt < moonPlanetConjunctionBracketAttempts; attempt++ {
|
|
left, right, ok := moonPlanetConjunctionFindBracket(guessTT, halfSpan, moonPlanetConjunctionBracketStepDays, planet, inst)
|
|
if ok {
|
|
eventUT := moonPlanetConjunctionEventUT(left, right, planet, inst)
|
|
if math.IsNaN(eventUT) {
|
|
halfSpan *= moonPlanetConjunctionBracketGrowth
|
|
continue
|
|
}
|
|
if moonPlanetConjunctionInDirection(eventUT, queryTT, direction) {
|
|
return eventUT
|
|
}
|
|
}
|
|
halfSpan *= moonPlanetConjunctionBracketGrowth
|
|
}
|
|
return moonPlanetConjunctionDirectionalFallback(queryTT, planet, direction, inst)
|
|
}
|
|
|
|
func moonPlanetConjunctionDirectionalEvent(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int, instances ...*Instance) float64 {
|
|
inst := instanceValue(instances)
|
|
|
|
return moonPlanetConjunctionDirectionalEventWithLocal(queryTT, planet, direction, moonPlanetConjunctionMaybeLocalEvents(queryTT, planet, inst), inst)
|
|
}
|
|
|
|
// LastMoonPlanetConjunctionWithInstance 用指定星历实例求值;inst 为 nil 时用进程当前模型 / evaluation with an explicit instance; nil uses the process model.
|
|
func LastMoonPlanetConjunctionWithInstance(jde float64, planet MoonPlanetConjunctionPlanet, inst *Instance) float64 {
|
|
return moonPlanetConjunctionDirectionalEvent(jde, planet, -1, inst)
|
|
}
|
|
|
|
// NextMoonPlanetConjunctionWithInstance 用指定星历实例求值;inst 为 nil 时用进程当前模型 / evaluation with an explicit instance; nil uses the process model.
|
|
func NextMoonPlanetConjunctionWithInstance(jde float64, planet MoonPlanetConjunctionPlanet, inst *Instance) float64 {
|
|
return moonPlanetConjunctionDirectionalEvent(jde, planet, 1, inst)
|
|
}
|
|
|
|
// ClosestMoonPlanetConjunctionWithInstance 用指定星历实例求值;inst 为 nil 时用进程当前模型 / evaluation with an explicit instance; nil uses the process model.
|
|
func ClosestMoonPlanetConjunctionWithInstance(jde float64, planet MoonPlanetConjunctionPlanet, inst *Instance) float64 {
|
|
local := moonPlanetConjunctionMaybeLocalEvents(jde, planet, inst)
|
|
if !math.IsNaN(local.lastUT) && !math.IsNaN(local.nextUT) {
|
|
if sameEventJD(local.lastUT, local.nextUT) {
|
|
return local.lastUT
|
|
}
|
|
return closestEventUTToQueryTT(jde, local.lastUT, local.nextUT)
|
|
}
|
|
if !math.IsNaN(local.lastUT) {
|
|
return local.lastUT
|
|
}
|
|
if !math.IsNaN(local.nextUT) {
|
|
return local.nextUT
|
|
}
|
|
last := moonPlanetConjunctionDirectionalEventWithLocal(jde, planet, -1, local, inst)
|
|
next := moonPlanetConjunctionDirectionalEventWithLocal(jde, planet, 1, local, inst)
|
|
if math.IsNaN(last) {
|
|
return next
|
|
}
|
|
if math.IsNaN(next) {
|
|
return last
|
|
}
|
|
return closestEventUTToQueryTT(jde, last, next)
|
|
}
|