- 新增日食、月食、本地可见性、中心线、半影区域、SVG 图示与沙罗周期信息 - 新增行星冲合、留、方照、物理星历、视直径、相位、亮肢角、轨道节点等计算 - 新增木星伽利略卫星位置、现象与接触事件计算 - 新增恒星星表、星座判定、自行修正与观测辅助能力 - 新增 coord、formula、orbit、sundial、lite/sun、lite/moon 等扩展包 - 完善农历年号、月相英文别名、视差角、大气质量、折射、日晷与双星计算 - 增加 NASA、JPL Horizons、IMCCE 等回归测试数据与基线测试 - 重构基础算法文件组织,补充大量公开 API 注释和语义回归测试 - 更新中文和英文 README,补充示例、精度说明、SVG 配图
290 lines
10 KiB
Go
290 lines
10 KiB
Go
package basic
|
|
|
|
import (
|
|
"math"
|
|
"sort"
|
|
"time"
|
|
|
|
. "b612.me/astro/tools"
|
|
)
|
|
|
|
const (
|
|
moonMaxDeclinationMeanMonthDays = 27.321582247
|
|
moonMaxDeclinationBaseCycle = 1336.86
|
|
moonMaxDeclinationSearchSpan = 3
|
|
)
|
|
|
|
// DeclinationEvent 赤纬极值事件 / declination extremum event.
|
|
type DeclinationEvent struct {
|
|
// JDE 是事件发生时刻对应的世界时儒略日 / event time as UTC-based Julian day.
|
|
JDE float64
|
|
// Declination 是该时刻月心地心赤纬,单位度 / geocentric lunar declination at the event, in degrees.
|
|
Declination float64
|
|
}
|
|
|
|
type moonMaxDeclinationCoefficients struct {
|
|
D0 float64
|
|
M0 float64
|
|
MP0 float64
|
|
F0 float64
|
|
JDE0 float64
|
|
sign float64
|
|
tc [44]float64
|
|
dc [37]float64
|
|
}
|
|
|
|
var moonMaxDeclinationNorthCoefficients = moonMaxDeclinationCoefficients{
|
|
D0: 152.2029,
|
|
M0: 14.8591,
|
|
MP0: 4.6881,
|
|
F0: 325.8867,
|
|
JDE0: 2451562.5897,
|
|
sign: 1,
|
|
tc: [44]float64{
|
|
0.8975, -0.4726, -0.1030, -0.0976, -0.0462, -0.0461, -0.0438, 0.0162,
|
|
-0.0157, 0.0145, 0.0136, -0.0095, -0.0091, -0.0089, 0.0075, -0.0068,
|
|
0.0061, -0.0047, -0.0043, -0.0040, -0.0037, 0.0031, 0.0030, -0.0029,
|
|
-0.0029, -0.0027, 0.0024, -0.0021, 0.0019, 0.0018, 0.0018, 0.0017,
|
|
0.0017, -0.0014, 0.0013, 0.0013, 0.0012, 0.0011, -0.0011, 0.0010,
|
|
0.0010, -0.0009, 0.0007, -0.0007,
|
|
},
|
|
dc: [37]float64{
|
|
5.1093, 0.2658, 0.1448, -0.0322, 0.0133, 0.0125, -0.0124, -0.0101,
|
|
0.0097, -0.0087, 0.0074, 0.0067, 0.0063, 0.0060, -0.0057, -0.0056,
|
|
0.0052, 0.0041, -0.0040, 0.0038, -0.0034, -0.0029, 0.0029, -0.0028,
|
|
-0.0028, -0.0023, -0.0021, 0.0019, 0.0018, 0.0017, 0.0015, 0.0014,
|
|
-0.0012, -0.0012, -0.0010, -0.0010, 0.0006,
|
|
},
|
|
}
|
|
|
|
var moonMaxDeclinationSouthCoefficients = moonMaxDeclinationCoefficients{
|
|
D0: 345.6676,
|
|
M0: 1.3951,
|
|
MP0: 186.21,
|
|
F0: 145.1633,
|
|
JDE0: 2451548.9289,
|
|
sign: -1,
|
|
tc: [44]float64{
|
|
-0.8975, -0.4726, -0.1030, -0.0976, 0.0541, 0.0516, -0.0438, 0.0112,
|
|
0.0157, 0.0023, -0.0136, 0.0110, 0.0091, 0.0089, 0.0075, -0.0030,
|
|
-0.0061, -0.0047, -0.0043, 0.0040, -0.0037, -0.0031, 0.0030, 0.0029,
|
|
-0.0029, -0.0027, 0.0024, -0.0021, -0.0019, -0.0006, -0.0018, -0.0017,
|
|
0.0017, 0.0014, -0.0013, -0.0013, 0.0012, 0.0011, 0.0011, 0.0010,
|
|
0.0010, -0.0009, -0.0007, -0.0007,
|
|
},
|
|
dc: [37]float64{
|
|
-5.1093, 0.2658, -0.1448, 0.0322, 0.0133, 0.0125, -0.0015, 0.0101,
|
|
-0.0097, 0.0087, 0.0074, 0.0067, -0.0063, -0.0060, 0.0057, -0.0056,
|
|
-0.0052, -0.0041, -0.0040, -0.0038, 0.0034, -0.0029, 0.0029, 0.0028,
|
|
-0.0028, 0.0023, 0.0021, 0.0019, 0.0018, -0.0017, 0.0015, 0.0014,
|
|
0.0012, -0.0012, 0.0010, -0.0010, 0.0037,
|
|
},
|
|
}
|
|
|
|
// MoonMaximumNorthDeclinations 指定年月内的所有月球最大北赤纬事件 / all maximum northern lunar declination events in the given Gregorian month.
|
|
func MoonMaximumNorthDeclinations(year int, month time.Month) []DeclinationEvent {
|
|
return moonMaximumDeclinationsInMonth(year, month, moonMaxDeclinationNorthCoefficients)
|
|
}
|
|
|
|
// MoonMaximumSouthDeclinations 指定年月内的所有月球最大南赤纬事件 / all maximum southern lunar declination events in the given Gregorian month.
|
|
func MoonMaximumSouthDeclinations(year int, month time.Month) []DeclinationEvent {
|
|
return moonMaximumDeclinationsInMonth(year, month, moonMaxDeclinationSouthCoefficients)
|
|
}
|
|
|
|
// LastMoonMaximumNorthDeclination 指定时刻之前最近一次月球最大北赤纬 / last maximum northern lunar declination at or before jd.
|
|
func LastMoonMaximumNorthDeclination(jd float64) DeclinationEvent {
|
|
return moonMaximumDeclinationSearch(jd, moonMaxDeclinationNorthCoefficients, -1, true)
|
|
}
|
|
|
|
// NextMoonMaximumNorthDeclination 指定时刻之后最近一次月球最大北赤纬 / next maximum northern lunar declination after jd.
|
|
func NextMoonMaximumNorthDeclination(jd float64) DeclinationEvent {
|
|
return moonMaximumDeclinationSearch(jd, moonMaxDeclinationNorthCoefficients, 1, false)
|
|
}
|
|
|
|
// ClosestMoonMaximumNorthDeclination 离指定时刻最近一次月球最大北赤纬 / closest maximum northern lunar declination to jd.
|
|
func ClosestMoonMaximumNorthDeclination(jd float64) DeclinationEvent {
|
|
return moonClosestMaximumDeclination(jd, moonMaxDeclinationNorthCoefficients)
|
|
}
|
|
|
|
// LastMoonMaximumSouthDeclination 指定时刻之前最近一次月球最大南赤纬 / last maximum southern lunar declination at or before jd.
|
|
func LastMoonMaximumSouthDeclination(jd float64) DeclinationEvent {
|
|
return moonMaximumDeclinationSearch(jd, moonMaxDeclinationSouthCoefficients, -1, true)
|
|
}
|
|
|
|
// NextMoonMaximumSouthDeclination 指定时刻之后最近一次月球最大南赤纬 / next maximum southern lunar declination after jd.
|
|
func NextMoonMaximumSouthDeclination(jd float64) DeclinationEvent {
|
|
return moonMaximumDeclinationSearch(jd, moonMaxDeclinationSouthCoefficients, 1, false)
|
|
}
|
|
|
|
// ClosestMoonMaximumSouthDeclination 离指定时刻最近一次月球最大南赤纬 / closest maximum southern lunar declination to jd.
|
|
func ClosestMoonMaximumSouthDeclination(jd float64) DeclinationEvent {
|
|
return moonClosestMaximumDeclination(jd, moonMaxDeclinationSouthCoefficients)
|
|
}
|
|
|
|
func moonMaximumDeclinationsInMonth(year int, month time.Month, coeffs moonMaxDeclinationCoefficients) []DeclinationEvent {
|
|
startUTC := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)
|
|
endUTC := startUTC.AddDate(0, 1, 0)
|
|
startTT := TD2UT(Date2JDE(startUTC), true)
|
|
endTT := TD2UT(Date2JDE(endUTC), true)
|
|
|
|
kStart := int(math.Floor((startTT-coeffs.JDE0)/moonMaxDeclinationMeanMonthDays)) - 1
|
|
kEnd := int(math.Ceil((endTT-coeffs.JDE0)/moonMaxDeclinationMeanMonthDays)) + 1
|
|
|
|
cfg := apsisSearchConfig{
|
|
bracketHalfWidth: moonApsisBracketHalfWidth,
|
|
sampleStep: moonApsisSampleStep,
|
|
derivativeStep: moonApsisDerivativeStep,
|
|
toleranceDays: moonApsisToleranceDays,
|
|
maxIterations: moonApsisMaxIterations,
|
|
maximize: coeffs.sign > 0,
|
|
}
|
|
|
|
events := make([]DeclinationEvent, 0, 2)
|
|
for k := kStart; k <= kEnd; k++ {
|
|
event := moonMaximumDeclinationEvent(k, coeffs, cfg)
|
|
eventTimeUTC := JDE2DateByZone(event.JDE, time.UTC, false)
|
|
if eventTimeUTC.Before(startUTC) || !eventTimeUTC.Before(endUTC) {
|
|
continue
|
|
}
|
|
events = append(events, event)
|
|
}
|
|
|
|
sort.Slice(events, func(i, j int) bool {
|
|
return events[i].JDE < events[j].JDE
|
|
})
|
|
return events
|
|
}
|
|
|
|
func moonMaximumDeclinationEvent(k int, coeffs moonMaxDeclinationCoefficients, cfg apsisSearchConfig) DeclinationEvent {
|
|
seedTT := moonMaximumDeclinationSeedTT(k, coeffs)
|
|
eventTT, declination := refineDistanceExtremum(seedTT, cfg, func(sampleTT float64) float64 {
|
|
return HMoonTrueDecN(sampleTT, -1)
|
|
})
|
|
return DeclinationEvent{
|
|
JDE: TD2UT(eventTT, false),
|
|
Declination: declination,
|
|
}
|
|
}
|
|
|
|
func moonMaximumDeclinationSearch(jd float64, coeffs moonMaxDeclinationCoefficients, direction int, includeCurrent bool) DeclinationEvent {
|
|
cfg := apsisSearchConfig{
|
|
bracketHalfWidth: moonApsisBracketHalfWidth,
|
|
sampleStep: moonApsisSampleStep,
|
|
derivativeStep: moonApsisDerivativeStep,
|
|
toleranceDays: moonApsisToleranceDays,
|
|
maxIterations: moonApsisMaxIterations,
|
|
maximize: coeffs.sign > 0,
|
|
}
|
|
targetTT := TD2UT(jd, true)
|
|
centerK := int(math.Round((targetTT - coeffs.JDE0) / moonMaxDeclinationMeanMonthDays))
|
|
|
|
found := false
|
|
bestDistance := math.Inf(1)
|
|
var best DeclinationEvent
|
|
for offset := -moonMaxDeclinationSearchSpan; offset <= moonMaxDeclinationSearchSpan; offset++ {
|
|
event := moonMaximumDeclinationEvent(centerK+offset, coeffs, cfg)
|
|
delta := event.JDE - jd
|
|
if !moonMaximumDeclinationMatchesDirection(delta, direction, includeCurrent) {
|
|
continue
|
|
}
|
|
distance := math.Abs(delta)
|
|
if !found || distance < bestDistance || (distance == bestDistance && moonMaximumDeclinationEarlier(event, best)) {
|
|
best = event
|
|
bestDistance = distance
|
|
found = true
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
func moonClosestMaximumDeclination(jd float64, coeffs moonMaxDeclinationCoefficients) DeclinationEvent {
|
|
last := moonMaximumDeclinationSearch(jd, coeffs, -1, true)
|
|
next := moonMaximumDeclinationSearch(jd, coeffs, 1, false)
|
|
lastDistance := math.Abs(jd - last.JDE)
|
|
nextDistance := math.Abs(next.JDE - jd)
|
|
if lastDistance <= nextDistance {
|
|
return last
|
|
}
|
|
return next
|
|
}
|
|
|
|
func moonMaximumDeclinationMatchesDirection(delta float64, direction int, includeCurrent bool) bool {
|
|
switch direction {
|
|
case -1:
|
|
if includeCurrent {
|
|
return delta <= 0
|
|
}
|
|
return delta < 0
|
|
case 1:
|
|
if includeCurrent {
|
|
return delta >= 0
|
|
}
|
|
return delta > 0
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func moonMaximumDeclinationEarlier(a, b DeclinationEvent) bool {
|
|
return a.JDE < b.JDE
|
|
}
|
|
|
|
func moonMaximumDeclinationSeedTT(k int, coeffs moonMaxDeclinationCoefficients) float64 {
|
|
cycle := float64(k)
|
|
T := cycle / moonMaxDeclinationBaseCycle
|
|
D := Limit360(coeffs.D0 + 333.0705546*cycle - 0.0004214*T*T + 0.00000011*T*T*T)
|
|
M := Limit360(coeffs.M0 + 26.9281592*cycle - 0.0000355*T*T - 0.0000001*T*T*T)
|
|
MP := Limit360(coeffs.MP0 + 356.9562794*cycle + 0.0103066*T*T + 0.00001251*T*T*T)
|
|
F := Limit360(coeffs.F0 + 1.4467807*cycle - 0.0020690*T*T - 0.00000215*T*T*T)
|
|
E := 1 - 0.002516*T - 0.0000074*T*T
|
|
|
|
return coeffs.JDE0 +
|
|
moonMaxDeclinationMeanMonthDays*cycle +
|
|
0.000119804*T*T -
|
|
0.000000141*T*T*T +
|
|
coeffs.tc[0]*Cos(F) +
|
|
coeffs.tc[1]*Sin(MP) +
|
|
coeffs.tc[2]*Sin(2*F) +
|
|
coeffs.tc[3]*Sin(2*D-MP) +
|
|
coeffs.tc[4]*Cos(MP-F) +
|
|
coeffs.tc[5]*Cos(MP+F) +
|
|
coeffs.tc[6]*Sin(2*D) +
|
|
coeffs.tc[7]*Sin(M)*E +
|
|
coeffs.tc[8]*Cos(3*F) +
|
|
coeffs.tc[9]*Sin(MP+2*F) +
|
|
coeffs.tc[10]*Cos(2*D-F) +
|
|
coeffs.tc[11]*Cos(2*D-MP-F) +
|
|
coeffs.tc[12]*Cos(2*D-MP+F) +
|
|
coeffs.tc[13]*Cos(2*D+F) +
|
|
coeffs.tc[14]*Sin(2*MP) +
|
|
coeffs.tc[15]*Sin(MP-2*F) +
|
|
coeffs.tc[16]*Cos(2*MP-F) +
|
|
coeffs.tc[17]*Sin(MP+3*F) +
|
|
coeffs.tc[18]*Sin(2*D-M-MP)*E +
|
|
coeffs.tc[19]*Cos(MP-2*F) +
|
|
coeffs.tc[20]*Sin(2*(D-MP)) +
|
|
coeffs.tc[21]*Sin(F) +
|
|
coeffs.tc[22]*Sin(2*D+MP) +
|
|
coeffs.tc[23]*Cos(MP+2*F) +
|
|
coeffs.tc[24]*Sin(2*D-M)*E +
|
|
coeffs.tc[25]*Sin(MP+F) +
|
|
coeffs.tc[26]*Sin(M-MP)*E +
|
|
coeffs.tc[27]*Sin(MP-3*F) +
|
|
coeffs.tc[28]*Sin(2*MP+F) +
|
|
coeffs.tc[29]*Cos(2*(D-MP)-F) +
|
|
coeffs.tc[30]*Sin(3*F) +
|
|
coeffs.tc[31]*Cos(MP+3*F) +
|
|
coeffs.tc[32]*Cos(2*MP) +
|
|
coeffs.tc[33]*Cos(2*D-MP) +
|
|
coeffs.tc[34]*Cos(2*D+MP+F) +
|
|
coeffs.tc[35]*Cos(MP) +
|
|
coeffs.tc[36]*Sin(3*MP+F) +
|
|
coeffs.tc[37]*Sin(2*D-MP+F) +
|
|
coeffs.tc[38]*Cos(2*(D-MP)) +
|
|
coeffs.tc[39]*Cos(D+F) +
|
|
coeffs.tc[40]*Sin(M+MP)*E +
|
|
coeffs.tc[41]*Sin(2*(D-F)) +
|
|
coeffs.tc[42]*Cos(2*MP+F) +
|
|
coeffs.tc[43]*Cos(3*MP+F)
|
|
}
|