Files
astro/eclipse/solar_shadow.go
T

314 lines
13 KiB
Go
Raw Permalink Normal View History

package eclipse
import (
"math"
"time"
"b612.me/astro/basic"
)
// SolarEclipseShadowKind 阴影类型 / shadow kind.
type SolarEclipseShadowKind int
const (
// SolarEclipseShadowUmbra 本影与反本影 / umbra and antumbra.
SolarEclipseShadowUmbra SolarEclipseShadowKind = iota
// SolarEclipseShadowPenumbra 半影,即偏食区 / penumbra, the partial-eclipse region.
SolarEclipseShadowPenumbra
)
// SolarEclipseShadowSolverOptions 单时刻阴影求解器配置 / single-instant shadow solver options.
type SolarEclipseShadowSolverOptions struct {
// Model 月亮半径模型,零值为 NASA bulletin Split-K / lunar radius model.
Model SolarEclipseRadiusModel
// DeltaTSeconds 显式 ΔT(秒),<=0 用进程级模型,只改变地球自转相位 / explicit ΔT in seconds.
DeltaTSeconds float64
// BoundaryPoints 边界角向采样点数,<=0 用 96 / boundary sample count.
BoundaryPoints int
// Kind 本影或半影,零值为本影 / umbra or penumbra, zero is the umbra.
Kind SolarEclipseShadowKind
// TargetSpacingKM 边界加密目标间距(千米),0 用该类型的整包采样默认值,负值表示不加密 / boundary refinement spacing in km.
TargetSpacingKM float64
}
// SolarEclipseShadowTopology 瞬时足迹拓扑签名,用于插值门控 / footprint topology signature.
type SolarEclipseShadowTopology struct {
// Kind 阴影类型 / shadow kind.
Kind SolarEclipseShadowKind
// Vertices、Segments 与 Closed 描述边界形状 / boundary shape.
Vertices int
Segments int
Closed bool
// EnclosesPole 环绕过极点,经度已展开,不能按下标插值 / pole-winding ring.
EnclosesPole bool
}
// Signature 稳定签名字符串,可直接作为能否插值的比较键 / stable interpolation key.
func (topology SolarEclipseShadowTopology) Signature() string {
return basic.SolarEclipseShadowTopology{
Kind: basic.SolarEclipseShadowKind(topology.Kind),
Vertices: topology.Vertices,
Segments: topology.Segments,
Closed: topology.Closed,
EnclosesPole: topology.EnclosesPole,
}.Signature()
}
// SolarEclipseShadowInstant 某瞬时的全球本影或半影足迹 / instantaneous shadow footprint.
type SolarEclipseShadowInstant struct {
// Time 记录对应的时刻:UTC 入口为调用方所给,TT 入口回填民用时刻(TT2UTC),与句柄 ΔT 无关。
// Time is the instant this record describes: the caller's value for the UTC entry, and the civil
// time recovered with TT2UTC for the TT entry, independently of the handle's ΔT.
Time time.Time
// JDE 几何使用的力学时儒略日,ΔT 不改变它 / TT instant used by the geometry.
JDE float64
// DeltaTSeconds 实际使用的 ΔT(秒),来自句柄显式值或进程级模型 / ΔT actually used.
DeltaTSeconds float64
// Model 本次使用的月亮半径模型 / lunar radius model used.
Model SolarEclipseRadiusModel
// Kind 本次计算的阴影类型 / shadow kind of this computation.
Kind SolarEclipseShadowKind
// Closed 边界由阴影自身闭合,false 表示被地平线切断 / self-closed or horizon-cut.
Closed bool
// Boundaries 物理边界分段,HorizonEnds 是两端的地平擦地点 / segments and grazing points.
Boundaries [][]SolarEclipsePathPoint
HorizonEnds []SolarEclipsePathPoint
// Topology 插值判定用的拓扑签名 / interpolation signature.
Topology SolarEclipseShadowTopology
}
// Empty 该时刻阴影未落在地球表面 / no footprint on the Earth.
func (instant SolarEclipseShadowInstant) Empty() bool {
return len(instant.Boundaries) == 0
}
// SolarEclipseStationState 某瞬时的站心日月几何 / topocentric geometry at one instant.
type SolarEclipseStationState struct {
// Time 是该状态对应的时刻。
// Time is the instant this state describes.
Time time.Time
// JDE 本次计算使用的力学时儒略日 / TT instant used.
JDE float64
// DeltaTSeconds 实际使用的 ΔT(秒) / ΔT actually used.
DeltaTSeconds float64
// SeparationDeg 与 SeparationArcsec 是日月中心的站心角距 / topocentric separation.
SeparationDeg float64
SeparationArcsec float64
// 三个半径字段是日月站心视半径 / topocentric apparent radii.
SunRadiusDeg float64
MoonOuterRadiusDeg float64
MoonInnerRadiusDeg float64
// SunAltitudeDeg 与 SunAzimuthDeg 是站心太阳高度角与方位角,方位角自北向东 / solar altitude and azimuth.
SunAltitudeDeg float64
SunAzimuthDeg float64
// Magnitude 瞬时食分,Obscuration 太阳视面积遮蔽率 / instantaneous magnitude and obscuration.
Magnitude float64
Obscuration float64
// 中心食标志:位于本影或反本影内,以及全食或环食 / central phase flags.
InCentralPhase bool
HasTotalPhase bool
HasAnnularPhase bool
// CentralPhaseType 中心食类型,非中心食为 SolarEclipseNone / central phase kind.
CentralPhaseType SolarEclipseType
// Visible 太阳中心在地平线上,高度用俯仰角修正 / Sun center above the horizon.
Visible bool
}
// SolarEclipseShadowSolver 可复用的单时刻求解器;非并发安全,宿主每条 lane 各持一个 / reusable solver, one per lane.
type SolarEclipseShadowSolver struct {
options SolarEclipseShadowSolverOptions
inner *basic.SolarEclipseShadowSolver
}
// NewSolarEclipseShadowSolver 构造单时刻求解器 / builds a single-instant solver.
func NewSolarEclipseShadowSolver(options SolarEclipseShadowSolverOptions) *SolarEclipseShadowSolver {
return &SolarEclipseShadowSolver{
options: options,
inner: basic.NewSolarEclipseShadowSolver(basic.SolarEclipseShadowSolverOptions{
Model: basic.SolarEclipseRadiusModel(options.Model),
DeltaTSeconds: options.DeltaTSeconds,
BoundaryPoints: options.BoundaryPoints,
Kind: basic.SolarEclipseShadowKind(options.Kind),
TargetSpacingKM: options.TargetSpacingKM,
}),
}
}
func (solver *SolarEclipseShadowSolver) ttJDE(value time.Time) float64 {
// 入参是民用时刻:TT = UTC + (TT−UTC),与闰秒表一致。显式 ΔT 是 TT−UT1,
// 只改自转相位,不能掺进这条换算,否则报告值与实用偏移会差一个 DUT1。
return basic.UTC2TT(basic.Date2JD(value.UTC()))
}
// ShadowAt 给定 UTC 时刻的阴影足迹;不在地球上时返回 (零值, false) / footprint at one UTC instant.
func (solver *SolarEclipseShadowSolver) ShadowAt(value time.Time) (SolarEclipseShadowInstant, bool) {
return solver.shadowAt(value, solver.ttJDE(value))
}
// ShadowAtJDE 以 TT 时刻取足迹,ΔT 只改变地面位置 / footprint at one TT instant.
func (solver *SolarEclipseShadowSolver) ShadowAtJDE(jdeTT float64) (SolarEclipseShadowInstant, bool) {
if !finiteEclipseValue(jdeTT) {
return SolarEclipseShadowInstant{}, false
}
return solver.shadowAt(time.Time{}, jdeTT)
}
func (solver *SolarEclipseShadowSolver) shadowAt(
value time.Time,
jdeTT float64,
) (SolarEclipseShadowInstant, bool) {
instant, ok := solver.inner.ShadowAtJDE(jdeTT)
return solarEclipseShadowInstantFromBasic(value, instant), ok
}
func solarEclipseShadowInstantFromBasic(
value time.Time, instant basic.SolarEclipseShadowInstant,
) SolarEclipseShadowInstant {
location := value.Location()
if value.IsZero() {
value = solarEclipseTTJDEToTime(instant.JDE, time.UTC)
location = time.UTC
}
return SolarEclipseShadowInstant{
Time: value,
JDE: instant.JDE,
DeltaTSeconds: instant.DeltaTSeconds,
Model: mapBasicSolarEclipseModel(instant.Model),
Kind: SolarEclipseShadowKind(instant.Kind),
Closed: instant.Closed,
Boundaries: solarEclipsePathSegmentsFromBasic(instant.Boundaries, location),
HorizonEnds: solarEclipsePathPointsFromBasic(instant.HorizonEnds, location),
Topology: SolarEclipseShadowTopology{
Kind: SolarEclipseShadowKind(instant.Topology.Kind),
Vertices: instant.Topology.Vertices,
Segments: instant.Topology.Segments,
Closed: instant.Topology.Closed,
EnclosesPole: instant.Topology.EnclosesPole,
},
}
}
// StationStateAt 给定 UTC 时刻与站点的站心情形,任何时刻可调用 / topocentric state at one instant.
func (solver *SolarEclipseShadowSolver) StationStateAt(
value time.Time, lon, lat, height float64,
) SolarEclipseStationState {
return solver.stationState(value, solver.ttJDE(value), lon, lat, height)
}
// StationStateAtJDE 以 TT 时刻取站心情形,ΔT 只影响地球自转相位 / topocentric state at one TT instant.
func (solver *SolarEclipseShadowSolver) StationStateAtJDE(
jdeTT, lon, lat, height float64,
) SolarEclipseStationState {
if !finiteEclipseValue(jdeTT) {
return SolarEclipseStationState{}
}
state := solver.inner.StationStateAtJDE(jdeTT, lon, lat, height)
result := solarEclipseStationStateFromBasic(time.Time{}, state)
result.Time = solarEclipseTTJDEToTime(jdeTT, time.UTC)
return result
}
func (solver *SolarEclipseShadowSolver) stationState(
value time.Time, jdeTT, lon, lat, height float64,
) SolarEclipseStationState {
return solarEclipseStationStateFromBasic(value, solver.inner.StationStateAtJDE(jdeTT, lon, lat, height))
}
func solarEclipseStationStateFromBasic(
value time.Time, state basic.SolarEclipseStationState,
) SolarEclipseStationState {
return SolarEclipseStationState{
Time: value,
JDE: state.JDE,
DeltaTSeconds: state.DeltaTSeconds,
SeparationDeg: state.SeparationDeg,
SeparationArcsec: state.SeparationArcsec,
SunRadiusDeg: state.SunRadiusDeg,
MoonOuterRadiusDeg: state.MoonOuterRadiusDeg,
MoonInnerRadiusDeg: state.MoonInnerRadiusDeg,
SunAltitudeDeg: state.SunAltitudeDeg,
SunAzimuthDeg: state.SunAzimuthDeg,
Magnitude: state.Magnitude,
Obscuration: state.Obscuration,
InCentralPhase: state.InCentralPhase,
HasTotalPhase: state.HasTotalPhase,
HasAnnularPhase: state.HasAnnularPhase,
CentralPhaseType: mapBasicSolarEclipseType(state.CentralPhaseType),
Visible: state.Visible,
}
}
// SolarEclipseShadowAt 无状态便捷入口,可并发调用 / stateless convenience entry point.
func SolarEclipseShadowAt(value time.Time) (SolarEclipseShadowInstant, bool) {
return NewSolarEclipseShadowSolver(SolarEclipseShadowSolverOptions{}).ShadowAt(value)
}
// SolarEclipseStationStateAt 无状态便捷入口,可并发调用 / stateless convenience entry point.
func SolarEclipseStationStateAt(value time.Time, lon, lat, height float64) SolarEclipseStationState {
return NewSolarEclipseShadowSolver(SolarEclipseShadowSolverOptions{}).StationStateAt(value, lon, lat, height)
}
func finiteEclipseValue(value float64) bool {
return !math.IsNaN(value) && !math.IsInf(value, 0)
}
// ShadowBetween 区间内等步长逐时刻的阴影足迹,超过内部上限时返回 nil / per-step footprints over a range.
func (solver *SolarEclipseShadowSolver) ShadowBetween(
start, end time.Time, step time.Duration,
) []SolarEclipseShadowInstant {
if step <= 0 || !end.After(start) {
return nil
}
startTT := solver.ttJDE(start)
endTT := solver.ttJDE(end)
stepDays := step.Hours() / 24
if stepDays <= 0 {
return nil
}
inner := solver.inner.ShadowBetweenJDE(startTT, endTT, stepDays)
result := make([]SolarEclipseShadowInstant, 0, len(inner))
// 逐条累加而非 index*step:Duration 是 int64 纳秒,长窗口下乘法会溢出。
instantTime := start
for _, instant := range inner {
// 直接映射,避免为每个时刻重复解算一次足迹。
result = append(result, solarEclipseShadowInstantFromBasic(instantTime, instant))
instantTime = instantTime.Add(step)
}
return result
}
// StationStatesBetween 区间内等步长逐时刻的站心情形 / per-step station states.
func (solver *SolarEclipseShadowSolver) StationStatesBetween(
start, end time.Time, step time.Duration, lon, lat, height float64,
) []SolarEclipseStationState {
if step <= 0 || !end.After(start) {
return nil
}
startTT := solver.ttJDE(start)
endTT := solver.ttJDE(end)
stepDays := step.Hours() / 24
inner := solver.inner.StationStatesBetweenJDE(startTT, endTT, stepDays, lon, lat, height)
result := make([]SolarEclipseStationState, 0, len(inner))
// 同上:累加避免长窗口下的纳秒溢出。
stateTime := start
for _, state := range inner {
result = append(result, solarEclipseStationStateFromBasic(stateTime, state))
stateTime = stateTime.Add(step)
}
return result
}
// SolarEclipseShadowBetween 无状态批量入口,使用进程级 ΔT / stateless batch entry point.
func SolarEclipseShadowBetween(
start, end time.Time, step time.Duration,
) []SolarEclipseShadowInstant {
return SolarEclipseShadowBetweenWithOptions(start, end, step, SolarEclipseShadowSolverOptions{})
}
// SolarEclipseShadowBetweenWithOptions 可显式指定 ΔT 与模型 / with explicit ΔT and model.
func SolarEclipseShadowBetweenWithOptions(
start, end time.Time, step time.Duration, options SolarEclipseShadowSolverOptions,
) []SolarEclipseShadowInstant {
return NewSolarEclipseShadowSolver(options).ShadowBetween(start, end, step)
}