astro/jupiter/phenomena_contact_events.go
starainrt 3ffdbe0034
feat: 扩展天文计算能力
- 新增日食、月食、本地可见性、中心线、半影区域、SVG 图示与沙罗周期信息
- 新增行星冲合、留、方照、物理星历、视直径、相位、亮肢角、轨道节点等计算
- 新增木星伽利略卫星位置、现象与接触事件计算
- 新增恒星星表、星座判定、自行修正与观测辅助能力
- 新增 coord、formula、orbit、sundial、lite/sun、lite/moon 等扩展包
- 完善农历年号、月相英文别名、视差角、大气质量、折射、日晷与双星计算
- 增加 NASA、JPL Horizons、IMCCE 等回归测试数据与基线测试
- 重构基础算法文件组织,补充大量公开 API 注释和语义回归测试
- 更新中文和英文 README,补充示例、精度说明、SVG 配图
2026-05-01 22:38:44 +08:00

126 lines
5.1 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 jupiter
import (
"time"
"b612.me/astro/basic"
)
// GalileanPhenomenonContactPhase 接触阶段 / contact phase.
type GalileanPhenomenonContactPhase string
const (
// GalileanPhenomenonContactDisappearance 初亏/初入接触阶段 / disappearance ingress contact.
GalileanPhenomenonContactDisappearance GalileanPhenomenonContactPhase = "disappearance"
// GalileanPhenomenonContactReappearance 复圆/复出接触阶段 / reappearance egress contact.
GalileanPhenomenonContactReappearance GalileanPhenomenonContactPhase = "reappearance"
)
// GalileanPhenomenonContact 伽利略卫星接触窗口 / Galilean-satellite contact window.
//
// Start/End 是有限圆盘或有限影斑开始/结束接触的时刻ModelCrossing 是这套连续接触模型下,
// 零半径参考点穿越边界的时刻。
// Start/End mark the beginning/end of the finite-disk or finite-shadow contact interval.
// ModelCrossing is the zero-radius boundary crossing in this continuous contact model.
type GalileanPhenomenonContact struct {
Valid bool
Phase GalileanPhenomenonContactPhase
Start time.Time
ModelCrossing time.Time
End time.Time
Duration time.Duration
}
// GalileanPhenomenonContactEvent IMCCE 风格的 D/F 接触事件 / IMCCE-style D/F contact event.
//
// 这个 API 返回有限圆盘/有限影斑的接触窗口,适合和 IMCCE 的 `TR.D/TR.F/OC.D/OC.F/EC.D/EC.F/SH.D/SH.F` 对齐;
// 现有 `GalileanPhenomenonEvent` 返回的是零半径几何模型保持 active 的整段区间,两者语义不同。
// 其中 `shadow_transit` 先用半影/本影边界求部分相持续时间,再把这段持续时间中心放在旧影轴过盘时刻上。
// This API returns finite-disk / finite-shadow contact windows and is intended to align with IMCCE
// `TR.D/TR.F/OC.D/OC.F/EC.D/EC.F/SH.D/SH.F` rows. The existing `GalileanPhenomenonEvent` returns the
// whole active interval of the zero-radius geometric model, so the semantics are different.
// For `shadow_transit`, the partial-phase duration comes from penumbra/umbra boundaries,
// while the reported D/F time is centered on the shadow-axis limb crossing from the existing full-event model.
type GalileanPhenomenonContactEvent struct {
Valid bool
Satellite int
Type GalileanPhenomenonType
Disappearance GalileanPhenomenonContact
Greatest time.Time
Reappearance GalileanPhenomenonContact
GreatestState GalileanSatellitePhenomenon
}
// LastGalileanPhenomenonContactEvent 上一次 IMCCE 风格接触事件 / previous IMCCE-style contact event.
func LastGalileanPhenomenonContactEvent(date time.Time, satellite int, phenomenonType GalileanPhenomenonType) GalileanPhenomenonContactEvent {
return galileanPhenomenonContactEventFromBasic(
basic.LastJupiterGalileanPhenomenonContactEvent(
basic.Date2JDE(date.UTC()),
satellite,
basic.JupiterGalileanPhenomenonType(phenomenonType),
),
date.Location(),
)
}
// NextGalileanPhenomenonContactEvent 下一次 IMCCE 风格接触事件 / next IMCCE-style contact event.
func NextGalileanPhenomenonContactEvent(date time.Time, satellite int, phenomenonType GalileanPhenomenonType) GalileanPhenomenonContactEvent {
return galileanPhenomenonContactEventFromBasic(
basic.NextJupiterGalileanPhenomenonContactEvent(
basic.Date2JDE(date.UTC()),
satellite,
basic.JupiterGalileanPhenomenonType(phenomenonType),
),
date.Location(),
)
}
// ClosestGalileanPhenomenonContactEvent 最近一次 IMCCE 风格接触事件 / closest IMCCE-style contact event.
func ClosestGalileanPhenomenonContactEvent(date time.Time, satellite int, phenomenonType GalileanPhenomenonType) GalileanPhenomenonContactEvent {
return galileanPhenomenonContactEventFromBasic(
basic.ClosestJupiterGalileanPhenomenonContactEvent(
basic.Date2JDE(date.UTC()),
satellite,
basic.JupiterGalileanPhenomenonType(phenomenonType),
),
date.Location(),
)
}
func galileanPhenomenonContactEventFromBasic(event basic.JupiterGalileanPhenomenonContactEvent, loc *time.Location) GalileanPhenomenonContactEvent {
if !event.Valid {
return GalileanPhenomenonContactEvent{}
}
greatest := basic.JDE2DateByZone(event.Greatest, loc, false)
return GalileanPhenomenonContactEvent{
Valid: true,
Satellite: event.Satellite,
Type: GalileanPhenomenonType(event.Type),
Disappearance: galileanPhenomenonContactFromBasic(event.Disappearance, loc),
Greatest: greatest,
Reappearance: galileanPhenomenonContactFromBasic(event.Reappearance, loc),
GreatestState: galileanPhenomenonFromBasic(event.GreatestPhenomenon),
}
}
func galileanPhenomenonContactFromBasic(contact basic.JupiterGalileanPhenomenonContact, loc *time.Location) GalileanPhenomenonContact {
if !contact.Valid {
return GalileanPhenomenonContact{}
}
start := basic.JDE2DateByZone(contact.Start, loc, false)
modelCrossing := basic.JDE2DateByZone(contact.ModelCrossing, loc, false)
end := basic.JDE2DateByZone(contact.End, loc, false)
return GalileanPhenomenonContact{
Valid: true,
Phase: GalileanPhenomenonContactPhase(contact.Phase),
Start: start,
ModelCrossing: modelCrossing,
End: end,
Duration: end.Sub(start),
}
}