Files
astro/eclipse/solar.go
T
b612 044f2f4381 feat: 引入可切换星历模型并扩展天象观测链路
- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算
- 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询
- 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出
- 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解
- 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤
- 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持
- 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理
- 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
2026-10-15 23:54:15 +08:00

515 lines
22 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package eclipse
import (
"math"
"time"
"b612.me/astro/basic"
)
const (
solarEclipseSynodicMonthDays = 29.530588853
solarEclipseSearchLimit = 36
solarEclipseSearchEpsilonDay = 1e-8
solarEclipseLatitudeLimitDeg = 2.0
)
type solarEclipseCalculator func(float64) basic.SolarEclipseResult
// SolarEclipseRadiusModel 日食月亮半径模型, lunar radius model for solar eclipses.
type SolarEclipseRadiusModel string
const (
// SolarEclipseModelIAUSingleK IAU 单一 k 值模型, IAU single-k model.
SolarEclipseModelIAUSingleK SolarEclipseRadiusModel = "iau_single_k"
// SolarEclipseModelNASABulletinSplitK NASA bulletin 分裂 k 值模型, NASA bulletin split-k model.
SolarEclipseModelNASABulletinSplitK SolarEclipseRadiusModel = "nasa_bulletin_split_k"
)
// SolarEclipseSunRadiusModel 日食太阳半径口径, solar radius convention for solar eclipses.
type SolarEclipseSunRadiusModel = basic.SolarEclipseSunRadiusModel
const (
// SolarEclipseSunRadiusStandard 标准档,1 AU 处 959.639″, standard solar radius for catalogue reproduction.
SolarEclipseSunRadiusStandard = basic.SolarEclipseSunRadiusStandard
// SolarEclipseSunRadiusMeasured 边缘档,1 AU 处 959.95″, measured eclipse solar radius.
SolarEclipseSunRadiusMeasured = basic.SolarEclipseSunRadiusMeasured
)
// SolarEclipseOptions 日食搜索的半径口径, radius conventions for a solar eclipse search.
type SolarEclipseOptions struct {
// RadiusModel 月亮半径模型,非 IAU Single-K 一律按 NASA bulletin Split-K, lunar radius model.
RadiusModel SolarEclipseRadiusModel
// SunRadiusModel 太阳半径口径,零值为标准档, solar radius convention.
SunRadiusModel SolarEclipseSunRadiusModel
// LunarLimb 月缘剖面,nil 沿用模块设置;修正站心接触、食分与遮蔽率,以及全球始末与食甚点中心食时长 / lunar limb profile for local contacts and values, global P1/P4 and central duration.
LunarLimb LunarLimb
}
// LunarLimb 月缘剖面接口,内置实现见 b612.me/astro/moon/limb, lunar limb profile interface.
type LunarLimb = basic.LunarLimb
// SolarEclipseLimbContact 月缘修正后的全球偏食始末接触点位置 / limb-corrected global contact location.
type SolarEclipseLimbContact = basic.SolarEclipseLimbContact
// SolarEclipseType 全局日食食型, global solar eclipse type.
type SolarEclipseType string
const (
// SolarEclipseNone 无日食, no solar eclipse.
SolarEclipseNone SolarEclipseType = "none"
// SolarEclipsePartial 日偏食, partial solar eclipse.
SolarEclipsePartial SolarEclipseType = "partial"
// SolarEclipseAnnular 日环食, annular solar eclipse.
SolarEclipseAnnular SolarEclipseType = "annular"
// SolarEclipseTotal 日全食, total solar eclipse.
SolarEclipseTotal SolarEclipseType = "total"
// SolarEclipseHybrid 全环食, hybrid solar eclipse.
SolarEclipseHybrid SolarEclipseType = "hybrid"
)
// SolarEclipseCentrality 中心线进入地球的方式, global eclipse centrality.
type SolarEclipseCentrality string
const (
// SolarEclipseNonCentral 非中心食, non-central eclipse.
SolarEclipseNonCentral SolarEclipseCentrality = "non_central"
// SolarEclipseCentralOneLimit 单界中心食, central eclipse with one limit.
SolarEclipseCentralOneLimit SolarEclipseCentrality = "central_one_limit"
// SolarEclipseCentralTwoLimits 双界中心食, central eclipse with two limits.
SolarEclipseCentralTwoLimits SolarEclipseCentrality = "central_two_limits"
)
// SolarEclipseInfo 全局日食信息, global solar eclipse information.
//
// 所有时刻字段都保持用户输入的时区。
// 不存在的阶段使用零值 time.Time。
type SolarEclipseInfo struct {
// Model 日食月亮半径模型, eclipse lunar radius model.
Model SolarEclipseRadiusModel
// SunRadiusModel 日食太阳半径口径, solar radius convention.
SunRadiusModel SolarEclipseSunRadiusModel
// Type 全局食型, global eclipse type.
Type SolarEclipseType
// Centrality 中心性, eclipse centrality.
Centrality SolarEclipseCentrality
// LunarLimbApplied 全球偏食始末与食甚点中心食时长已按月缘剖面修正;剖面失败时为 false 且结果保持圆月面 / contacts refined with a lunar limb profile.
LunarLimbApplied bool
// PartialBeginContact / PartialEndContact 月缘修正后的偏食始末接触点位置,未修正时 Defined 为 false / limb-corrected contact locations.
PartialBeginContact SolarEclipseLimbContact
PartialEndContact SolarEclipseLimbContact
// HasSaros 存在沙罗序列信息(可能是锚点外推结果), has Saros series metadata (possibly extrapolated).
HasSaros bool
// Saros 是沙罗序列信息,包括系列号、系列内序号和总成员数。
// Saros is Saros series metadata with the series number, member index, and total member count.
Saros SarosInfo
// GreatestEclipse 食甚时刻, greatest eclipse.
GreatestEclipse time.Time
// PartialBeginOnEarth 地球范围偏食始, partial eclipse begins on Earth.
PartialBeginOnEarth time.Time
// PartialEndOnEarth 地球范围偏食终, partial eclipse ends on Earth.
PartialEndOnEarth time.Time
// CentralBeginOnEarth 地球范围中心食始, central eclipse begins on Earth.
CentralBeginOnEarth time.Time
// CentralEndOnEarth 地球范围中心食终, central eclipse ends on Earth.
CentralEndOnEarth time.Time
// GreatestJDE 是食甚的力学时儒略日;与 DeltaTSeconds 配对即可复现上列民用时刻。
// GreatestJDE is the TT Julian ephemeris day of greatest eclipse; with DeltaTSeconds it reproduces the civil times above.
GreatestJDE float64
// DeltaTSeconds 是本次计算实际使用的 TT−UT1,单位秒。
// DeltaTSeconds is the TT−UT1 used by this computation, in seconds.
DeltaTSeconds float64
// Magnitude 全局食分, global eclipse magnitude.
Magnitude float64
// Gamma 食甚时影轴到地心的距离, gamma at greatest eclipse.
Gamma float64
// CentralDuration 是食甚点的中心食持续时间,即日食目录(如 NASA「Central Dur.」)
// 的取值口径;沿整条中心线的最大值见 SolarEclipsePath.MaxCentralDuration。
// CentralDuration is the central-phase duration at the greatest eclipse, the
// value catalogues publish; SolarEclipsePath.MaxCentralDuration is the maximum
// along the whole track.
CentralDuration time.Duration
// PathWidthKM 是食甚处中心食带宽度;非中心食为 0;单侧极限(中心带仅触及地球边缘)时该解析式失效
// 并一并置 0,此时 PathWidthDefined 为 false,NASA 目录该栏印 '-'。
// PathWidthKM is the central path width at greatest eclipse, 0 for a non-central
// event, and 0 when the analytic formula fails at a single-sided limit where the
// band only grazes the Earth's limb; PathWidthDefined is false there and
// catalogues print '-' for this column.
PathWidthKM float64
// PathWidthDefined 表示上面的带宽是否有定义:只有南北两限都存在时才为 true。
// PathWidthDefined reports whether the width above is defined: it is true only
// when both band limits exist.
PathWidthDefined bool
// GreatestLongitude 食甚点经度,东正西负, longitude of greatest eclipse, east positive.
GreatestLongitude float64
// GreatestLatitude 食甚点纬度,北正南负, latitude of greatest eclipse, north positive.
GreatestLatitude float64
// HasPartial 存在偏食阶段, has partial phase.
HasPartial bool
// HasCentral 存在中心食阶段, has central phase.
HasCentral bool
// HasAnnular 存在环食阶段, has annular phase.
HasAnnular bool
// HasTotal 存在全食阶段, has total phase.
HasTotal bool
// HasHybrid 为混合食, is hybrid eclipse.
HasHybrid bool
}
// SolarEclipseOnDate 当地自然日全局日食查询 / local-date global solar eclipse query.
func (module *Eclipse) SolarEclipseOnDate(date time.Time) (SolarEclipseInfo, bool) {
return module.SolarEclipseOnDateNASABulletinSplitK(date)
}
// SolarEclipseOnDateNASABulletinSplitK 当地自然日全局日食查询(NASA bulletin Split-K) / local-date global solar eclipse query with NASA bulletin Split-K.
func (module *Eclipse) SolarEclipseOnDateNASABulletinSplitK(date time.Time) (SolarEclipseInfo, bool) {
return solarEclipseOnDate(date, func(seedJDE float64) basic.SolarEclipseResult {
return module.solarResult(seedJDE, basic.SolarEclipseModelNASABulletinSplitK)
}, module)
}
// SolarEclipseOnDateIAUSingleK 当地自然日全局日食查询(IAU Single-K) / local-date global solar eclipse query with IAU Single-K.
func (module *Eclipse) SolarEclipseOnDateIAUSingleK(date time.Time) (SolarEclipseInfo, bool) {
return solarEclipseOnDate(date, func(seedJDE float64) basic.SolarEclipseResult {
return module.solarResult(seedJDE, basic.SolarEclipseModelIAUSingleK)
}, module)
}
// SolarEclipseOnDateWithOptions 当地自然日全局日食查询(自定义半径口径) / local-date global solar eclipse query with custom radius conventions.
func (module *Eclipse) SolarEclipseOnDateWithOptions(date time.Time, options SolarEclipseOptions) (SolarEclipseInfo, bool) {
return solarEclipseOnDate(date, solarEclipseCalculatorFor(options, module), module)
}
// LastSolarEclipseWithOptions 上次日食(自定义半径口径) / previous solar eclipse with custom radius conventions.
func (module *Eclipse) LastSolarEclipseWithOptions(date time.Time, options SolarEclipseOptions) SolarEclipseInfo {
info, _ := searchSolarEclipse(date, -1, true, solarEclipseCalculatorFor(options, module), module)
return info
}
// NextSolarEclipseWithOptions 下次日食(自定义半径口径) / next solar eclipse with custom radius conventions.
func (module *Eclipse) NextSolarEclipseWithOptions(date time.Time, options SolarEclipseOptions) SolarEclipseInfo {
info, _ := searchSolarEclipse(date, 1, false, solarEclipseCalculatorFor(options, module), module)
return info
}
// ClosestSolarEclipseWithOptions 最近一次日食(自定义半径口径) / closest solar eclipse with custom radius conventions.
func (module *Eclipse) ClosestSolarEclipseWithOptions(date time.Time, options SolarEclipseOptions) SolarEclipseInfo {
last, hasLast := searchSolarEclipse(date, -1, true, solarEclipseCalculatorFor(options, module), module)
next, hasNext := searchSolarEclipse(date, 1, false, solarEclipseCalculatorFor(options, module), module)
return closestSolarEclipse(date, last, hasLast, next, hasNext)
}
func solarEclipseCalculatorFor(options SolarEclipseOptions, modules ...*Eclipse) solarEclipseCalculator {
module := &defaultModule
if len(modules) > 0 {
module = modules[0]
}
basicOptions := basic.SolarEclipseOptions{
RadiusModel: basic.SolarEclipseRadiusModel(options.RadiusModel),
SunRadiusModel: basic.SolarEclipseSunRadiusModel(options.SunRadiusModel),
}
if basicOptions.RadiusModel != basic.SolarEclipseModelIAUSingleK {
basicOptions.RadiusModel = basic.SolarEclipseModelNASABulletinSplitK
}
basicOptions.LunarLimb = options.LunarLimb
if basicOptions.LunarLimb == nil {
basicOptions.LunarLimb = module.lunarLimb
}
return func(seed float64) basic.SolarEclipseResult {
return basic.SolarEclipseWithOptionsWithInstance(seed, basicOptions, module.inst)
}
}
// solarResult 按模块星历与月缘计算一次全球日食;未设置月缘时与包级计算相同。
func (module *Eclipse) solarResult(seedJDE float64, model basic.SolarEclipseRadiusModel) basic.SolarEclipseResult {
if module.lunarLimb == nil {
if model == basic.SolarEclipseModelIAUSingleK {
return basic.SolarEclipseIAUSingleKWithInstance(seedJDE, module.inst)
}
return basic.SolarEclipseNASABulletinSplitKWithInstance(seedJDE, module.inst)
}
return basic.SolarEclipseWithOptionsWithInstance(seedJDE,
basic.SolarEclipseOptions{RadiusModel: model, LunarLimb: module.lunarLimb}, module.inst)
}
func solarEclipseOnDate(date time.Time, calculator solarEclipseCalculator, modules ...*Eclipse) (SolarEclipseInfo, bool) {
module := &defaultModule
if len(modules) > 0 {
module = modules[0]
}
location := date.Location()
dayStart, dayMid, dayEnd := solarEclipseLocalDayBounds(date)
candidateTT := basic.CalcMoonSHByJDEWithInstance(solarEclipseTimeToTTJDE(dayMid), 0, module.inst)
result := calculator(candidateTT)
if result.Type == basic.SolarEclipseNone {
return SolarEclipseInfo{}, false
}
info := solarEclipseInfoFromBasic(result, location, module)
if !solarEclipseOverlapsDate(info, dayStart, dayEnd) {
return SolarEclipseInfo{}, false
}
return info, true
}
// LastSolarEclipse 上次日食 / previous solar eclipse.
func (module *Eclipse) LastSolarEclipse(date time.Time) SolarEclipseInfo {
return module.LastSolarEclipseNASABulletinSplitK(date)
}
// LastSolarEclipseNASABulletinSplitK 上次日食(NASA bulletin Split-K) / previous solar eclipse with NASA bulletin Split-K.
func (module *Eclipse) LastSolarEclipseNASABulletinSplitK(date time.Time) SolarEclipseInfo {
info, _ := searchSolarEclipse(date, -1, true, func(seedJDE float64) basic.SolarEclipseResult {
return module.solarResult(seedJDE, basic.SolarEclipseModelNASABulletinSplitK)
}, module)
return info
}
// LastSolarEclipseIAUSingleK 上次日食(IAU Single-K) / previous solar eclipse with IAU Single-K.
func (module *Eclipse) LastSolarEclipseIAUSingleK(date time.Time) SolarEclipseInfo {
info, _ := searchSolarEclipse(date, -1, true, func(seedJDE float64) basic.SolarEclipseResult {
return module.solarResult(seedJDE, basic.SolarEclipseModelIAUSingleK)
}, module)
return info
}
// NextSolarEclipse 下次日食 / next solar eclipse.
func (module *Eclipse) NextSolarEclipse(date time.Time) SolarEclipseInfo {
return module.NextSolarEclipseNASABulletinSplitK(date)
}
// NextSolarEclipseNASABulletinSplitK 下次日食(NASA bulletin Split-K) / next solar eclipse with NASA bulletin Split-K.
func (module *Eclipse) NextSolarEclipseNASABulletinSplitK(date time.Time) SolarEclipseInfo {
info, _ := searchSolarEclipse(date, 1, false, func(seedJDE float64) basic.SolarEclipseResult {
return module.solarResult(seedJDE, basic.SolarEclipseModelNASABulletinSplitK)
}, module)
return info
}
// NextSolarEclipseIAUSingleK 下次日食(IAU Single-K) / next solar eclipse with IAU Single-K.
func (module *Eclipse) NextSolarEclipseIAUSingleK(date time.Time) SolarEclipseInfo {
info, _ := searchSolarEclipse(date, 1, false, func(seedJDE float64) basic.SolarEclipseResult {
return module.solarResult(seedJDE, basic.SolarEclipseModelIAUSingleK)
}, module)
return info
}
// ClosestSolarEclipse 最近一次日食 / closest solar eclipse.
func (module *Eclipse) ClosestSolarEclipse(date time.Time) SolarEclipseInfo {
return module.ClosestSolarEclipseNASABulletinSplitK(date)
}
// ClosestSolarEclipseNASABulletinSplitK 最近一次日食(NASA bulletin Split-K) / closest solar eclipse with NASA bulletin Split-K.
func (module *Eclipse) ClosestSolarEclipseNASABulletinSplitK(date time.Time) SolarEclipseInfo {
last, hasLast := searchSolarEclipse(date, -1, true, func(seedJDE float64) basic.SolarEclipseResult {
return module.solarResult(seedJDE, basic.SolarEclipseModelNASABulletinSplitK)
}, module)
next, hasNext := searchSolarEclipse(date, 1, false, func(seedJDE float64) basic.SolarEclipseResult {
return module.solarResult(seedJDE, basic.SolarEclipseModelNASABulletinSplitK)
}, module)
return closestSolarEclipse(date, last, hasLast, next, hasNext)
}
// ClosestSolarEclipseIAUSingleK 最近一次日食(IAU Single-K) / closest solar eclipse with IAU Single-K.
func (module *Eclipse) ClosestSolarEclipseIAUSingleK(date time.Time) SolarEclipseInfo {
last, hasLast := searchSolarEclipse(date, -1, true, func(seedJDE float64) basic.SolarEclipseResult {
return module.solarResult(seedJDE, basic.SolarEclipseModelIAUSingleK)
}, module)
next, hasNext := searchSolarEclipse(date, 1, false, func(seedJDE float64) basic.SolarEclipseResult {
return module.solarResult(seedJDE, basic.SolarEclipseModelIAUSingleK)
}, module)
return closestSolarEclipse(date, last, hasLast, next, hasNext)
}
func closestSolarEclipse(
date time.Time,
last SolarEclipseInfo,
hasLast bool,
next SolarEclipseInfo,
hasNext bool,
) SolarEclipseInfo {
switch {
case hasLast && !hasNext:
return last
case !hasLast && hasNext:
return next
case !hasLast && !hasNext:
return SolarEclipseInfo{}
}
lastDistance := math.Abs(date.Sub(last.GreatestEclipse).Seconds())
nextDistance := math.Abs(next.GreatestEclipse.Sub(date).Seconds())
if lastDistance <= nextDistance {
return last
}
return next
}
func searchSolarEclipse(
date time.Time,
direction int,
includeCurrent bool,
calculator solarEclipseCalculator, modules ...*Eclipse) (SolarEclipseInfo, bool) {
module := &defaultModule
if len(modules) > 0 {
module = modules[0]
}
targetTT := solarEclipseTimeToTTJDE(date)
candidateTT := basic.CalcMoonSHByJDEWithInstance(targetTT, 0, module.inst)
for i := 0; i < solarEclipseSearchLimit; i++ {
if module.isPotentialSolarEclipse(candidateTT) {
result := calculator(candidateTT)
if result.Type != basic.SolarEclipseNone && solarEclipseMatchesDirection(result.GreatestEclipse, targetTT, direction, includeCurrent) {
return solarEclipseInfoFromBasic(result, date.Location(), module), true
}
}
candidateTT = nextEclipseSearchCandidateTT(candidateTT, 0, direction, solarEclipseSynodicMonthDays, module)
}
return SolarEclipseInfo{}, false
}
func isPotentialSolarEclipse(newMoonTT float64) bool {
return defaultModule.isPotentialSolarEclipse(newMoonTT)
}
func (module *Eclipse) isPotentialSolarEclipse(newMoonTT float64) bool {
return math.Abs(basic.HMoonTrueBoWithInstance(newMoonTT, module.inst)) <= solarEclipseLatitudeLimitDeg
}
func solarEclipseMatchesDirection(greatestTT, targetTT float64, direction int, includeCurrent bool) bool {
delta := greatestTT - targetTT
if math.Abs(delta) <= solarEclipseSearchEpsilonDay {
return direction < 0 && includeCurrent
}
if direction > 0 {
return delta > 0
}
return delta < 0
}
func solarEclipseLocalDayBounds(date time.Time) (time.Time, time.Time, time.Time) {
location := date.Location()
dayStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, location)
dayMid := time.Date(date.Year(), date.Month(), date.Day(), 12, 0, 0, 0, location)
dayEnd := time.Date(date.Year(), date.Month(), date.Day()+1, 0, 0, 0, 0, location)
return dayStart, dayMid, dayEnd
}
func nextSolarEclipseLocalDayStart(dayStart time.Time) time.Time {
location := dayStart.Location()
return time.Date(dayStart.Year(), dayStart.Month(), dayStart.Day()+1, 0, 0, 0, 0, location)
}
func solarEclipseInfoFromBasic(result basic.SolarEclipseResult, location *time.Location, modules ...*Eclipse) SolarEclipseInfo {
module := &defaultModule
if len(modules) > 0 {
module = modules[0]
}
saros, hasSaros := solarSarosInfo(result.GreatestEclipse, module)
return SolarEclipseInfo{
Model: mapBasicSolarEclipseModel(result.Model),
SunRadiusModel: result.SunRadiusModel,
Type: mapBasicSolarEclipseType(result.Type),
Centrality: mapBasicSolarEclipseCentrality(result.Centrality),
LunarLimbApplied: result.LunarLimbApplied,
PartialBeginContact: result.PartialBeginContact,
PartialEndContact: result.PartialEndContact,
HasSaros: hasSaros,
Saros: saros,
GreatestEclipse: solarEclipseTTJDEToTime(result.GreatestEclipse, location),
PartialBeginOnEarth: solarEclipseTTJDEToTime(result.PartialBeginOnEarth, location),
PartialEndOnEarth: solarEclipseTTJDEToTime(result.PartialEndOnEarth, location),
CentralBeginOnEarth: solarEclipseTTJDEToTime(result.CentralBeginOnEarth, location),
CentralEndOnEarth: solarEclipseTTJDEToTime(result.CentralEndOnEarth, location),
GreatestJDE: result.GreatestEclipse,
DeltaTSeconds: basic.DeltaT(result.GreatestEclipse, true),
Magnitude: result.Magnitude,
Gamma: result.Gamma,
CentralDuration: solarEclipseDurationFromDays(result.CentralDurationDays),
PathWidthKM: result.PathWidthKM,
PathWidthDefined: result.PathWidthDefined,
GreatestLongitude: result.GreatestLongitude,
GreatestLatitude: result.GreatestLatitude,
HasPartial: result.HasPartial,
HasCentral: result.HasCentral,
HasAnnular: result.HasAnnular,
HasTotal: result.HasTotal,
HasHybrid: result.HasHybrid,
}
}
func mapBasicSolarEclipseModel(model basic.SolarEclipseRadiusModel) SolarEclipseRadiusModel {
switch model {
case basic.SolarEclipseModelIAUSingleK:
return SolarEclipseModelIAUSingleK
default:
return SolarEclipseModelNASABulletinSplitK
}
}
func mapBasicSolarEclipseType(eclipseType basic.SolarEclipseType) SolarEclipseType {
switch eclipseType {
case basic.SolarEclipsePartial:
return SolarEclipsePartial
case basic.SolarEclipseAnnular:
return SolarEclipseAnnular
case basic.SolarEclipseTotal:
return SolarEclipseTotal
case basic.SolarEclipseHybrid:
return SolarEclipseHybrid
default:
return SolarEclipseNone
}
}
func mapBasicSolarEclipseCentrality(centrality basic.SolarEclipseCentrality) SolarEclipseCentrality {
switch centrality {
case basic.SolarEclipseCentralOneLimit:
return SolarEclipseCentralOneLimit
case basic.SolarEclipseCentralTwoLimits:
return SolarEclipseCentralTwoLimits
default:
return SolarEclipseNonCentral
}
}
func solarEclipseOverlapsDate(info SolarEclipseInfo, dayStart, dayEnd time.Time) bool {
eventStart, eventEnd, ok := solarEclipseRange(info)
if !ok {
return false
}
return !eventEnd.Before(dayStart) && eventStart.Before(dayEnd)
}
func solarEclipseRange(info SolarEclipseInfo) (time.Time, time.Time, bool) {
if !info.HasPartial {
return time.Time{}, time.Time{}, false
}
return info.PartialBeginOnEarth, info.PartialEndOnEarth, true
}
func solarEclipseTTJDEToTime(ttJDE float64, location *time.Location) time.Time {
// 回填的是民用时刻,偏移必须与正向的 UTC2TT 成对(窗口内即闰秒表,窗口外按未来政策)。
// 用 ΔT(TT−UT1)会让时刻整体偏一个 DUT1,并破坏等时线网格的整刻度对齐。
if ttJDE == 0 || math.IsNaN(ttJDE) || math.IsInf(ttJDE, 0) {
return time.Time{}
}
return basic.JD2DateByZone(basic.TT2UTC(ttJDE), location, false)
}
func solarEclipseTimeToTTJDE(date time.Time) float64 {
utcJD := basic.Date2JD(date.UTC())
return basic.UTC2TT(utcJD)
}