2bf8478639
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
336 lines
12 KiB
Go
336 lines
12 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 {
|
|
if !finite(jd) {
|
|
// 非有限查询算不出周期序号(centerK 会把 NaN/±Inf 压成 MinInt64,种子外推到 1e40 后步长小于一个 ULP)。
|
|
return DeclinationEvent{}
|
|
}
|
|
cfg := moonMaximumDeclinationSearchConfig(coeffs)
|
|
centerK := moonMaximumDeclinationOffset(jd, coeffs)
|
|
step := 1
|
|
if direction < 0 {
|
|
step = -1
|
|
}
|
|
event := moonMaximumDeclinationEvent(centerK, coeffs, cfg)
|
|
// 事件时刻随周期序号单调递增:该方向上最近的候选就是 centerK 沿该方向第一个满足方向不变量
|
|
// 的周期;命中后只需再回退检查更早的周期是否同样满足,可达范围仍旧是 ±3 个周期。
|
|
if !moonMaximumDeclinationMatchesDirection(event.JDE-jd, direction, includeCurrent) {
|
|
for offset := 1; offset <= moonMaxDeclinationSearchSpan; offset++ {
|
|
event = moonMaximumDeclinationEvent(centerK+step*offset, coeffs, cfg)
|
|
if moonMaximumDeclinationMatchesDirection(event.JDE-jd, direction, includeCurrent) {
|
|
return event
|
|
}
|
|
}
|
|
return DeclinationEvent{}
|
|
}
|
|
for offset := 1; offset <= moonMaxDeclinationSearchSpan; offset++ {
|
|
previous := moonMaximumDeclinationEvent(centerK-step*offset, coeffs, cfg)
|
|
if !moonMaximumDeclinationMatchesDirection(previous.JDE-jd, direction, includeCurrent) {
|
|
break
|
|
}
|
|
event = previous
|
|
}
|
|
return event
|
|
}
|
|
|
|
func moonClosestMaximumDeclination(jd float64, coeffs moonMaxDeclinationCoefficients) DeclinationEvent {
|
|
if !finite(jd) {
|
|
return DeclinationEvent{}
|
|
}
|
|
cfg := moonMaximumDeclinationSearchConfig(coeffs)
|
|
centerK := moonMaximumDeclinationOffset(jd, coeffs)
|
|
center := moonMaximumDeclinationEvent(centerK, coeffs, cfg)
|
|
var last, next DeclinationEvent
|
|
if center.JDE <= jd {
|
|
last = center
|
|
next = moonMaximumDeclinationAround(jd, coeffs, cfg, centerK, 1, false)
|
|
} else {
|
|
next = center
|
|
last = moonMaximumDeclinationAround(jd, coeffs, cfg, centerK, -1, true)
|
|
}
|
|
lastDistance := math.Abs(jd - last.JDE)
|
|
nextDistance := math.Abs(next.JDE - jd)
|
|
if lastDistance <= nextDistance {
|
|
return last
|
|
}
|
|
return next
|
|
}
|
|
|
|
// moonMaximumDeclinationAround 从 centerK 沿 step 方向找第一个满足方向不变量的周期;±3 个周期内没有就返回零事件。
|
|
func moonMaximumDeclinationAround(jd float64, coeffs moonMaxDeclinationCoefficients, cfg apsisSearchConfig,
|
|
centerK, step int, includeCurrent bool) DeclinationEvent {
|
|
direction := 1
|
|
if step < 0 {
|
|
direction = -1
|
|
}
|
|
for offset := 1; offset <= moonMaxDeclinationSearchSpan; offset++ {
|
|
event := moonMaximumDeclinationEvent(centerK+step*offset, coeffs, cfg)
|
|
if moonMaximumDeclinationMatchesDirection(event.JDE-jd, direction, includeCurrent) {
|
|
return event
|
|
}
|
|
}
|
|
return DeclinationEvent{}
|
|
}
|
|
|
|
func moonMaximumDeclinationSearchConfig(coeffs moonMaxDeclinationCoefficients) apsisSearchConfig {
|
|
return apsisSearchConfig{
|
|
bracketHalfWidth: moonApsisBracketHalfWidth,
|
|
sampleStep: moonApsisSampleStep,
|
|
derivativeStep: moonApsisDerivativeStep,
|
|
toleranceDays: moonApsisToleranceDays,
|
|
maxIterations: moonApsisMaxIterations,
|
|
maximize: coeffs.sign > 0,
|
|
}
|
|
}
|
|
|
|
// moonMaximumDeclinationOffset 查询时刻落在哪个平均周期(种子多项式的中心序号)。
|
|
func moonMaximumDeclinationOffset(jd float64, coeffs moonMaxDeclinationCoefficients) int {
|
|
return int(math.Round((TD2UT(jd, true) - coeffs.JDE0) / moonMaxDeclinationMeanMonthDays))
|
|
}
|
|
|
|
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)
|
|
}
|