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), } }