Files
astro/eclipse/lunar_local.go
T
b612 2bf8478639 feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑
- 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口
- 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界
- 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名
- 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验
- 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口
- 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
2026-09-17 12:27:40 +08:00

500 lines
22 KiB
Go
Raw Permalink 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 localLunarEclipseSearchLimit = 6000
type localLunarEclipseQueryMode int
const (
localLunarEclipseQueryVisible localLunarEclipseQueryMode = iota
localLunarEclipseQueryGeometric
)
// LocalLunarEclipseInfo 站点月食信息, local lunar eclipse information.
//
// 所有时刻字段都保持用户输入的时区。
// 不存在的阶段使用零值 time.Time。
type LocalLunarEclipseInfo struct {
// Type 月食类型, eclipse type.
Type LunarEclipseType
// 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
// Longitude 观测点经度,东正西负, observer longitude, east positive.
Longitude float64
// Latitude 观测点纬度,北正南负, observer latitude, north positive.
Latitude float64
// Height 观测点海拔高度,单位米, observer height in meters.
Height float64
// PenumbralMagnitude 半影食分, penumbral magnitude.
PenumbralMagnitude float64
// UmbralMagnitude 本影食分;纯半影月食时可为负值, umbral magnitude; can be negative for purely penumbral eclipses.
UmbralMagnitude float64
// PenumbralStart 半影始, penumbral eclipse begins.
PenumbralStart time.Time
// PartialStart 初亏, partial eclipse begins.
PartialStart time.Time
// TotalStart 食既, total eclipse begins.
TotalStart time.Time
// Maximum 食甚, greatest eclipse.
Maximum time.Time
// TotalEnd 生光, total eclipse ends.
TotalEnd time.Time
// PartialEnd 复圆, partial eclipse ends.
PartialEnd time.Time
// PenumbralEnd 半影终, penumbral eclipse ends.
PenumbralEnd time.Time
// MoonAltitude 食甚时月亮高度角,单位度, Moon altitude at maximum in degrees.
MoonAltitude float64
// MoonAzimuth 食甚时月亮方位角,单位度, Moon azimuth at maximum in degrees.
MoonAzimuth float64
// VisibleAtMaximum 食甚时月亮中心在本地几何地平线上方, Moon center above the local geometric horizon at maximum.
VisibleAtMaximum bool
// HasPenumbral 有半影阶段, has penumbral phase.
HasPenumbral bool
// HasPartial 有偏食阶段, has partial phase.
HasPartial bool
// HasTotal 有全食阶段, has total phase.
HasTotal bool
}
// LocalLunarEclipseOnDate 当地可见月食查询 / local visible lunar eclipse query.
// Determine whether a visible local lunar eclipse occurs on the local date, using Danjon by default.
func LocalLunarEclipseOnDate(date time.Time, lon, lat, height float64) (LocalLunarEclipseInfo, bool) {
return LocalLunarEclipseOnDateDanjon(date, lon, lat, height)
}
// LocalLunarEclipseOnDateDanjon 当地可见月食查询(Danjon) / local visible lunar eclipse query with Danjon model.
// Determine whether a visible local lunar eclipse occurs on the local date with the Danjon model.
func LocalLunarEclipseOnDateDanjon(date time.Time, lon, lat, height float64) (LocalLunarEclipseInfo, bool) {
return localLunarEclipseOnDate(date, lon, lat, height, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
}
// LocalLunarEclipseOnDateChauvenet 当地可见月食查询(Chauvenet) / local visible lunar eclipse query with Chauvenet model.
// Determine whether a visible local lunar eclipse occurs on the local date with the Chauvenet model.
func LocalLunarEclipseOnDateChauvenet(date time.Time, lon, lat, height float64) (LocalLunarEclipseInfo, bool) {
return localLunarEclipseOnDate(date, lon, lat, height, basic.LunarEclipseChauvenet, localLunarEclipseQueryVisible)
}
// GeometricLocalLunarEclipseOnDate 当地几何月食查询 / local geometric lunar eclipse query.
// Determine whether a geometric local lunar eclipse occurs on the local date, using Danjon by default.
func GeometricLocalLunarEclipseOnDate(date time.Time, lon, lat, height float64) (LocalLunarEclipseInfo, bool) {
return GeometricLocalLunarEclipseOnDateDanjon(date, lon, lat, height)
}
// GeometricLocalLunarEclipseOnDateDanjon 当地几何月食查询(Danjon) / local geometric lunar eclipse query with Danjon model.
// Determine whether a geometric local lunar eclipse occurs on the local date with the Danjon model.
func GeometricLocalLunarEclipseOnDateDanjon(date time.Time, lon, lat, height float64) (LocalLunarEclipseInfo, bool) {
return localLunarEclipseOnDate(date, lon, lat, height, basic.LunarEclipseDanjon, localLunarEclipseQueryGeometric)
}
// GeometricLocalLunarEclipseOnDateChauvenet 当地几何月食查询(Chauvenet) / local geometric lunar eclipse query with Chauvenet model.
// Determine whether a geometric local lunar eclipse occurs on the local date with the Chauvenet model.
func GeometricLocalLunarEclipseOnDateChauvenet(date time.Time, lon, lat, height float64) (LocalLunarEclipseInfo, bool) {
return localLunarEclipseOnDate(date, lon, lat, height, basic.LunarEclipseChauvenet, localLunarEclipseQueryGeometric)
}
func localLunarEclipseOnDate(
date time.Time,
lon, lat, height float64,
calculator lunarEclipseCalculator,
mode localLunarEclipseQueryMode,
) (LocalLunarEclipseInfo, bool) {
location := date.Location()
dayStart, dayMid, dayEnd := lunarEclipseLocalDayBounds(date)
phaseDiff := moonSunLoDiff(dayMid)
if phaseDiff < lunarEclipseDayPhaseMin || phaseDiff > lunarEclipseDayPhaseMax {
return LocalLunarEclipseInfo{}, false
}
candidateTT := basic.CalcMoonSHByJDE(timeToTTJDE(dayMid), 1)
if !isPotentialLunarEclipse(candidateTT) {
return LocalLunarEclipseInfo{}, false
}
result := calculator(candidateTT)
if result.Type == basic.LunarEclipseNone {
return LocalLunarEclipseInfo{}, false
}
info := localLunarEclipseInfoFromBasic(result, lon, lat, height, location)
if !localLunarEclipseOverlapsDate(info, dayStart, dayEnd) {
return LocalLunarEclipseInfo{}, false
}
if mode == localLunarEclipseQueryVisible && !localLunarEclipseVisibleOnDate(info, dayStart, dayEnd) {
return LocalLunarEclipseInfo{}, false
}
return info, true
}
// LastLocalLunarEclipse 上次可见月食 / previous visible local lunar eclipse.
// Previous visible local lunar eclipse, using Danjon by default.
func LastLocalLunarEclipse(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
return LastLocalLunarEclipseDanjon(date, lon, lat, height)
}
// LastLocalTotalLunarEclipse 上次可见月全食 / previous visible local total lunar eclipse.
// Previous visible local total lunar eclipse, using Danjon by default.
func LastLocalTotalLunarEclipse(date time.Time, lon, lat, height float64) (LocalLunarEclipseInfo, bool) {
return searchLocalTotalLunarEclipse(date, lon, lat, height, -1, true, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
}
// LastLocalLunarEclipseDanjon 上次可见月食(Danjon) / previous visible local lunar eclipse with Danjon model.
// Previous visible local lunar eclipse with the Danjon model.
func LastLocalLunarEclipseDanjon(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
info, _ := searchLocalLunarEclipse(date, lon, lat, height, -1, true, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
return info
}
// LastLocalLunarEclipseChauvenet 上次可见月食(Chauvenet) / previous visible local lunar eclipse with Chauvenet model.
// Previous visible local lunar eclipse with the Chauvenet model.
func LastLocalLunarEclipseChauvenet(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
info, _ := searchLocalLunarEclipse(date, lon, lat, height, -1, true, basic.LunarEclipseChauvenet, localLunarEclipseQueryVisible)
return info
}
// LastGeometricLocalLunarEclipse 上次几何月食 / previous geometric local lunar eclipse.
// Previous geometric local lunar eclipse, using Danjon by default.
func LastGeometricLocalLunarEclipse(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
return LastGeometricLocalLunarEclipseDanjon(date, lon, lat, height)
}
// LastGeometricLocalLunarEclipseDanjon 上次几何月食(Danjon) / previous geometric local lunar eclipse with Danjon model.
// Previous geometric local lunar eclipse with the Danjon model.
func LastGeometricLocalLunarEclipseDanjon(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
info, _ := searchLocalLunarEclipse(date, lon, lat, height, -1, true, basic.LunarEclipseDanjon, localLunarEclipseQueryGeometric)
return info
}
// LastGeometricLocalLunarEclipseChauvenet 上次几何月食(Chauvenet) / previous geometric local lunar eclipse with Chauvenet model.
// Previous geometric local lunar eclipse with the Chauvenet model.
func LastGeometricLocalLunarEclipseChauvenet(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
info, _ := searchLocalLunarEclipse(date, lon, lat, height, -1, true, basic.LunarEclipseChauvenet, localLunarEclipseQueryGeometric)
return info
}
// NextLocalLunarEclipse 下次可见月食 / next visible local lunar eclipse.
// Next visible local lunar eclipse, using Danjon by default.
func NextLocalLunarEclipse(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
return NextLocalLunarEclipseDanjon(date, lon, lat, height)
}
// NextLocalTotalLunarEclipse 下次可见月全食 / next visible local total lunar eclipse.
// Next visible local total lunar eclipse, using Danjon by default.
func NextLocalTotalLunarEclipse(date time.Time, lon, lat, height float64) (LocalLunarEclipseInfo, bool) {
return searchLocalTotalLunarEclipse(date, lon, lat, height, 1, false, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
}
// NextLocalLunarEclipseDanjon 下次可见月食(Danjon) / next visible local lunar eclipse with Danjon model.
// Next visible local lunar eclipse with the Danjon model.
func NextLocalLunarEclipseDanjon(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
info, _ := searchLocalLunarEclipse(date, lon, lat, height, 1, false, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
return info
}
// NextLocalLunarEclipseChauvenet 下次可见月食(Chauvenet) / next visible local lunar eclipse with Chauvenet model.
// Next visible local lunar eclipse with the Chauvenet model.
func NextLocalLunarEclipseChauvenet(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
info, _ := searchLocalLunarEclipse(date, lon, lat, height, 1, false, basic.LunarEclipseChauvenet, localLunarEclipseQueryVisible)
return info
}
// NextGeometricLocalLunarEclipse 下次几何月食 / next geometric local lunar eclipse.
// Next geometric local lunar eclipse, using Danjon by default.
func NextGeometricLocalLunarEclipse(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
return NextGeometricLocalLunarEclipseDanjon(date, lon, lat, height)
}
// NextGeometricLocalLunarEclipseDanjon 下次几何月食(Danjon) / next geometric local lunar eclipse with Danjon model.
// Next geometric local lunar eclipse with the Danjon model.
func NextGeometricLocalLunarEclipseDanjon(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
info, _ := searchLocalLunarEclipse(date, lon, lat, height, 1, false, basic.LunarEclipseDanjon, localLunarEclipseQueryGeometric)
return info
}
// NextGeometricLocalLunarEclipseChauvenet 下次几何月食(Chauvenet) / next geometric local lunar eclipse with Chauvenet model.
// Next geometric local lunar eclipse with the Chauvenet model.
func NextGeometricLocalLunarEclipseChauvenet(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
info, _ := searchLocalLunarEclipse(date, lon, lat, height, 1, false, basic.LunarEclipseChauvenet, localLunarEclipseQueryGeometric)
return info
}
// ClosestLocalLunarEclipse 最近一次可见月食 / closest visible local lunar eclipse.
// Closest visible local lunar eclipse, using Danjon by default.
func ClosestLocalLunarEclipse(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
return ClosestLocalLunarEclipseDanjon(date, lon, lat, height)
}
// ClosestLocalTotalLunarEclipse 最近一次可见月全食 / closest visible local total lunar eclipse.
// Closest visible local total lunar eclipse, using Danjon by default.
func ClosestLocalTotalLunarEclipse(date time.Time, lon, lat, height float64) (LocalLunarEclipseInfo, bool) {
last, hasLast := searchLocalTotalLunarEclipse(date, lon, lat, height, -1, true, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
next, hasNext := searchLocalTotalLunarEclipse(date, lon, lat, height, 1, false, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
return closestLocalLunarEclipseResult(date, last, hasLast, next, hasNext)
}
// ClosestLocalLunarEclipseDanjon 最近一次可见月食(Danjon) / closest visible local lunar eclipse with Danjon model.
// Closest visible local lunar eclipse with the Danjon model.
func ClosestLocalLunarEclipseDanjon(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
last, hasLast := searchLocalLunarEclipse(date, lon, lat, height, -1, true, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
next, hasNext := searchLocalLunarEclipse(date, lon, lat, height, 1, false, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
return closestLocalLunarEclipse(date, last, hasLast, next, hasNext)
}
// ClosestLocalLunarEclipseChauvenet 最近一次可见月食(Chauvenet) / closest visible local lunar eclipse with Chauvenet model.
// Closest visible local lunar eclipse with the Chauvenet model.
func ClosestLocalLunarEclipseChauvenet(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
last, hasLast := searchLocalLunarEclipse(date, lon, lat, height, -1, true, basic.LunarEclipseChauvenet, localLunarEclipseQueryVisible)
next, hasNext := searchLocalLunarEclipse(date, lon, lat, height, 1, false, basic.LunarEclipseChauvenet, localLunarEclipseQueryVisible)
return closestLocalLunarEclipse(date, last, hasLast, next, hasNext)
}
// ClosestGeometricLocalLunarEclipse 最近一次几何月食 / closest geometric local lunar eclipse.
// Closest geometric local lunar eclipse, using Danjon by default.
func ClosestGeometricLocalLunarEclipse(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
return ClosestGeometricLocalLunarEclipseDanjon(date, lon, lat, height)
}
// ClosestGeometricLocalLunarEclipseDanjon 最近一次几何月食(Danjon) / closest geometric local lunar eclipse with Danjon model.
// Closest geometric local lunar eclipse with the Danjon model.
func ClosestGeometricLocalLunarEclipseDanjon(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
last, hasLast := searchLocalLunarEclipse(date, lon, lat, height, -1, true, basic.LunarEclipseDanjon, localLunarEclipseQueryGeometric)
next, hasNext := searchLocalLunarEclipse(date, lon, lat, height, 1, false, basic.LunarEclipseDanjon, localLunarEclipseQueryGeometric)
return closestLocalLunarEclipse(date, last, hasLast, next, hasNext)
}
// ClosestGeometricLocalLunarEclipseChauvenet 最近一次几何月食(Chauvenet) / closest geometric local lunar eclipse with Chauvenet model.
// Closest geometric local lunar eclipse with the Chauvenet model.
func ClosestGeometricLocalLunarEclipseChauvenet(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
last, hasLast := searchLocalLunarEclipse(date, lon, lat, height, -1, true, basic.LunarEclipseChauvenet, localLunarEclipseQueryGeometric)
next, hasNext := searchLocalLunarEclipse(date, lon, lat, height, 1, false, basic.LunarEclipseChauvenet, localLunarEclipseQueryGeometric)
return closestLocalLunarEclipse(date, last, hasLast, next, hasNext)
}
func closestLocalLunarEclipse(
date time.Time,
last LocalLunarEclipseInfo,
hasLast bool,
next LocalLunarEclipseInfo,
hasNext bool,
) LocalLunarEclipseInfo {
info, _ := closestLocalLunarEclipseResult(date, last, hasLast, next, hasNext)
return info
}
func closestLocalLunarEclipseResult(
date time.Time,
last LocalLunarEclipseInfo,
hasLast bool,
next LocalLunarEclipseInfo,
hasNext bool,
) (LocalLunarEclipseInfo, bool) {
switch {
case hasLast && !hasNext:
return last, true
case !hasLast && hasNext:
return next, true
case !hasLast && !hasNext:
return LocalLunarEclipseInfo{}, false
}
lastDistance := math.Abs(date.Sub(last.Maximum).Seconds())
nextDistance := math.Abs(next.Maximum.Sub(date).Seconds())
if lastDistance <= nextDistance {
return last, true
}
return next, true
}
func searchLocalLunarEclipse(
date time.Time,
lon, lat, height float64,
direction int,
includeCurrent bool,
calculator lunarEclipseCalculator,
mode localLunarEclipseQueryMode,
) (LocalLunarEclipseInfo, bool) {
targetTT := timeToTTJDE(date)
candidateTT := basic.CalcMoonSHByJDE(targetTT, 1)
for i := 0; i < localLunarEclipseSearchLimit; i++ {
if isPotentialLunarEclipse(candidateTT) {
result := calculator(candidateTT)
if result.Type != basic.LunarEclipseNone {
info := localLunarEclipseInfoFromBasic(result, lon, lat, height, date.Location())
if (mode != localLunarEclipseQueryVisible || localLunarEclipseVisible(info)) &&
lunarEclipseMatchesDirection(result.Maximum, targetTT, direction, includeCurrent) {
return info, true
}
}
}
candidateTT = nextEclipseSearchCandidateTT(candidateTT, 1, direction, lunarEclipseSynodicMonthDays)
}
return LocalLunarEclipseInfo{}, false
}
func searchLocalTotalLunarEclipse(
date time.Time,
lon, lat, height float64,
direction int,
includeCurrent bool,
calculator lunarEclipseCalculator,
mode localLunarEclipseQueryMode,
) (LocalLunarEclipseInfo, bool) {
targetTT := timeToTTJDE(date)
candidateTT := basic.CalcMoonSHByJDE(targetTT, 1)
for i := 0; i < localLunarEclipseSearchLimit; i++ {
if isPotentialLunarEclipse(candidateTT) {
result := calculator(candidateTT)
if result.HasTotal {
info := localLunarEclipseInfoFromBasic(result, lon, lat, height, date.Location())
if (mode != localLunarEclipseQueryVisible || localTotalLunarEclipseVisible(info)) &&
lunarEclipseMatchesDirection(result.Maximum, targetTT, direction, includeCurrent) {
return info, true
}
}
}
candidateTT = nextEclipseSearchCandidateTT(candidateTT, 1, direction, lunarEclipseSynodicMonthDays)
}
return LocalLunarEclipseInfo{}, false
}
func localLunarEclipseInfoFromBasic(
result basic.LunarEclipseResult,
lon, lat, height float64,
location *time.Location,
) LocalLunarEclipseInfo {
maximum := ttJDEToTime(result.Maximum, location)
visibleThreshold := localLunarEclipseVisibilityThreshold(height, lat)
moonAltitude := lunarAltitude(maximum, lon, lat)
saros, hasSaros := lunarSarosInfo(result.Maximum)
return LocalLunarEclipseInfo{
HasSaros: hasSaros,
Saros: saros,
Type: mapBasicLunarEclipseType(result.Type),
Longitude: lon,
Latitude: lat,
Height: height,
PenumbralMagnitude: result.PenumbralMagnitude,
UmbralMagnitude: result.Magnitude,
PenumbralStart: ttJDEToTime(result.PenumbralStart, location),
PartialStart: ttJDEToTime(result.PartialStart, location),
TotalStart: ttJDEToTime(result.TotalStart, location),
Maximum: maximum,
TotalEnd: ttJDEToTime(result.TotalEnd, location),
PartialEnd: ttJDEToTime(result.PartialEnd, location),
PenumbralEnd: ttJDEToTime(result.PenumbralEnd, location),
MoonAltitude: moonAltitude,
MoonAzimuth: lunarAzimuth(maximum, lon, lat),
VisibleAtMaximum: moonAltitude > visibleThreshold,
HasPenumbral: result.HasPenumbral,
HasPartial: result.HasPartial,
HasTotal: result.HasTotal,
}
}
func localLunarEclipseOverlapsDate(info LocalLunarEclipseInfo, dayStart, dayEnd time.Time) bool {
eventStart, eventEnd, ok := localLunarEclipseRange(info)
if !ok {
return false
}
return !eventEnd.Before(dayStart) && eventStart.Before(dayEnd)
}
func localLunarEclipseRange(info LocalLunarEclipseInfo) (time.Time, time.Time, bool) {
if !info.HasPenumbral {
return time.Time{}, time.Time{}, false
}
return info.PenumbralStart, info.PenumbralEnd, true
}
func localLunarEclipseVisible(info LocalLunarEclipseInfo) bool {
eventStart, eventEnd, ok := localLunarEclipseRange(info)
if !ok {
return false
}
return localLunarEclipseVisibleDuring(info, eventStart, eventEnd)
}
func localTotalLunarEclipseVisible(info LocalLunarEclipseInfo) bool {
if !info.HasTotal || info.TotalStart.IsZero() || info.TotalEnd.IsZero() {
return false
}
return localLunarEclipseVisibleDuring(info, info.TotalStart, info.TotalEnd)
}
func localLunarEclipseVisibleOnDate(info LocalLunarEclipseInfo, dayStart, dayEnd time.Time) bool {
eventStart, eventEnd, ok := localLunarEclipseRange(info)
if !ok {
return false
}
segmentStart := maxLocalLunarTime(eventStart, dayStart)
segmentEnd := minLocalLunarTime(eventEnd, dayEnd)
if !segmentStart.Before(segmentEnd) {
return false
}
return localLunarEclipseVisibleDuring(info, segmentStart, segmentEnd)
}
func localLunarEclipseVisibleDuring(info LocalLunarEclipseInfo, start, end time.Time) bool {
if localLunarEclipseAltitudeVisible(start, info) || localLunarEclipseAltitudeVisible(end, info) {
return true
}
for dayStart, _, _ := lunarEclipseLocalDayBounds(start); !dayStart.After(end); dayStart = nextLunarEclipseLocalDayStart(dayStart) {
_, culminationSeed, _ := lunarEclipseLocalDayBounds(dayStart)
culmination := lunarCulminationTime(culminationSeed, info.Longitude, info.Latitude)
if culmination.Before(start) || culmination.After(end) {
continue
}
if localLunarEclipseAltitudeVisible(culmination, info) {
return true
}
}
return false
}
func localLunarEclipseAltitudeVisible(date time.Time, info LocalLunarEclipseInfo) bool {
return lunarAltitude(date, info.Longitude, info.Latitude) > localLunarEclipseVisibilityThreshold(info.Height, info.Latitude)
}
func localLunarEclipseVisibilityThreshold(height, latitude float64) float64 {
if height <= 0 {
return 0
}
return -basic.HeightDegreeByLat(height, latitude)
}
func maxLocalLunarTime(a, b time.Time) time.Time {
if a.After(b) {
return a
}
return b
}
func minLocalLunarTime(a, b time.Time) time.Time {
if a.Before(b) {
return a
}
return b
}