312 lines
13 KiB
Go
312 lines
13 KiB
Go
|
|
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 入口按句柄 ΔT 换算 / instant this record describes.
|
|||
|
|
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.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 {
|
|||
|
|
utJDE := basic.Date2JDE(value.UTC())
|
|||
|
|
if solver.options.DeltaTSeconds > 0 {
|
|||
|
|
return utJDE + solver.options.DeltaTSeconds/86400
|
|||
|
|
}
|
|||
|
|
return basic.TD2UT(utJDE, true)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 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 = solarEclipseTTJDEToTimeWithDeltaT(instant.JDE, instant.DeltaTSeconds, 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 = solarEclipseTTJDEToTimeWithDeltaT(jdeTT, state.DeltaTSeconds, 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)
|
|||
|
|
}
|