feat: 增强日月食搜索、沙罗周期与内行星凌日

- 使用压缩表加速查找日月食沙罗周期信息
- 优化日月食搜索跳步,减少非食季朔望月扫描
- 新增本地日全食、日环食、月全食搜索接口,返回 ok 区分未找到结果
- 新增水星、金星地心凌日查询及测试
This commit is contained in:
2026-05-03 19:00:08 +08:00
parent 3ffdbe0034
commit bec7b8a0d8
20 changed files with 1987 additions and 408 deletions
+73
View File
@@ -0,0 +1,73 @@
package venus
import (
"time"
"b612.me/astro/basic"
)
// TransitInfo 地心金星凌日信息 / geocentric Venus transit information.
//
// Start、Greatest、End、InternalStart、InternalEnd 都保持调用者输入的时区。
// 内切接触不存在时 InternalStart / InternalEnd 为零值。
// Start, Greatest, End, InternalStart, and InternalEnd preserve the caller's timezone.
// InternalStart and InternalEnd are zero values when internal contacts do not exist.
type TransitInfo struct {
Valid bool
Start time.Time
InternalStart time.Time
Greatest time.Time
InternalEnd time.Time
End time.Time
Duration time.Duration
InternalDuration time.Duration
MinimumSeparationArcsec float64
SunSemidiameterArcsec float64
PlanetSemidiameterArcsec float64
HasInternal bool
}
// NextTransit 下一次地心金星凌日 / next geocentric Venus transit.
func NextTransit(date time.Time) TransitInfo {
return transitInfoFromBasic(basic.NextVenusTransit(basic.Date2JDE(date.UTC())), date.Location())
}
// LastTransit 上一次地心金星凌日 / previous geocentric Venus transit.
func LastTransit(date time.Time) TransitInfo {
return transitInfoFromBasic(basic.LastVenusTransit(basic.Date2JDE(date.UTC())), date.Location())
}
// ClosestTransit 最近一次地心金星凌日 / closest geocentric Venus transit.
func ClosestTransit(date time.Time) TransitInfo {
return transitInfoFromBasic(basic.ClosestVenusTransit(basic.Date2JDE(date.UTC())), date.Location())
}
func transitInfoFromBasic(result basic.PlanetTransitResult, loc *time.Location) TransitInfo {
if !result.Valid {
return TransitInfo{}
}
start := basic.JDE2DateByZone(result.ExternalIngress, loc, false)
greatest := basic.JDE2DateByZone(result.Greatest, loc, false)
end := basic.JDE2DateByZone(result.ExternalEgress, loc, false)
info := TransitInfo{
Valid: true,
Start: start,
Greatest: greatest,
End: end,
Duration: end.Sub(start),
MinimumSeparationArcsec: result.MinimumSeparationArcsec,
SunSemidiameterArcsec: result.SunSemidiameterArcsec,
PlanetSemidiameterArcsec: result.PlanetSemidiameterArcsec,
HasInternal: result.HasInternal,
}
if result.HasInternal {
info.InternalStart = basic.JDE2DateByZone(result.InternalIngress, loc, false)
info.InternalEnd = basic.JDE2DateByZone(result.InternalEgress, loc, false)
info.InternalDuration = info.InternalEnd.Sub(info.InternalStart)
}
return info
}
+23
View File
@@ -0,0 +1,23 @@
package venus
import (
"testing"
"time"
)
func TestTransitWrappers(t *testing.T) {
loc := time.FixedZone("CST", 8*3600)
info := NextTransit(time.Date(2012, 1, 1, 0, 0, 0, 0, loc))
if !info.Valid {
t.Fatal("expected valid transit")
}
if info.Greatest.Location() != loc {
t.Fatalf("timezone mismatch: got %v want %v", info.Greatest.Location(), loc)
}
if info.Greatest.Year() != 2012 || info.Greatest.Month() != time.June || info.Greatest.Day() != 6 {
t.Fatalf("unexpected greatest time: %s", info.Greatest)
}
if !info.HasInternal || info.Duration <= 0 || info.InternalDuration <= 0 {
t.Fatalf("unexpected durations: %+v", info)
}
}