feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
This commit is contained in:
@@ -0,0 +1,429 @@
|
||||
package basic
|
||||
|
||||
import "math"
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
const (
|
||||
solarEclipseShadowDefaultBoundaryPoints = 96
|
||||
// 开放足迹的物理边界含擦地点,点数上限沿用整包口径。
|
||||
solarEclipseShadowMaximumBoundaryPoints = 1440
|
||||
// 半个朔望月:窗口内缓存锚点必为最近朔月。
|
||||
solarEclipseShadowAnchorHalfWindowDays = 14.7652944265
|
||||
// 批量条数上限,超出返回 nil。
|
||||
solarEclipseShadowMaximumBatchCount = 262144
|
||||
)
|
||||
|
||||
// SolarEclipseShadowTopology 瞬时足迹拓扑签名,用于插值门控 / footprint topology signature.
|
||||
type SolarEclipseShadowTopology struct {
|
||||
// Kind 阴影类型,签名以 umbra 或 penumbra 开头 / shadow kind, the signature prefix.
|
||||
Kind SolarEclipseShadowKind
|
||||
// Vertices 物理边界顶点总数 / boundary vertex count.
|
||||
Vertices int
|
||||
// Segments 边界分段数,换日线会拆分 / segment count.
|
||||
Segments int
|
||||
// Closed 边界是否由阴影自身闭合 / self-closed boundary.
|
||||
Closed bool
|
||||
// EnclosesPole 环绕过极点,经度已展开,不能按下标插值 / pole-winding ring.
|
||||
EnclosesPole bool
|
||||
}
|
||||
|
||||
// Signature 稳定签名字符串,可直接作为能否插值的比较键 / stable interpolation key.
|
||||
func (topology SolarEclipseShadowTopology) Signature() string {
|
||||
if topology.Vertices == 0 {
|
||||
return "empty"
|
||||
}
|
||||
builder := make([]byte, 0, 32)
|
||||
if topology.Kind == SolarEclipseShadowPenumbra {
|
||||
builder = append(builder, "penumbra"...)
|
||||
} else {
|
||||
builder = append(builder, "umbra"...)
|
||||
}
|
||||
if topology.Closed {
|
||||
builder = append(builder, "-closed"...)
|
||||
} else {
|
||||
builder = append(builder, "-horizon"...)
|
||||
}
|
||||
builder = append(builder, "-seg"...)
|
||||
builder = appendInt(builder, topology.Segments)
|
||||
builder = append(builder, "-pt"...)
|
||||
builder = appendInt(builder, topology.Vertices)
|
||||
if topology.EnclosesPole {
|
||||
builder = append(builder, "-pole"...)
|
||||
}
|
||||
return string(builder)
|
||||
}
|
||||
|
||||
func appendInt(target []byte, value int) []byte {
|
||||
if value == 0 {
|
||||
return append(target, '0')
|
||||
}
|
||||
if value < 0 {
|
||||
target = append(target, '-')
|
||||
value = -value
|
||||
}
|
||||
var digits [20]byte
|
||||
position := len(digits)
|
||||
for value > 0 {
|
||||
position--
|
||||
digits[position] = byte('0' + value%10)
|
||||
value /= 10
|
||||
}
|
||||
return append(target, digits[position:]...)
|
||||
}
|
||||
|
||||
// SolarEclipseShadowInstant 某瞬时(TT)的全球本影或半影足迹 / instantaneous shadow footprint.
|
||||
type SolarEclipseShadowInstant struct {
|
||||
// JDE 输入的力学时儒略日 / requested TT instant.
|
||||
JDE float64
|
||||
// Model 本次使用的月亮半径模型 / lunar radius model used.
|
||||
Model SolarEclipseRadiusModel
|
||||
// DeltaTSeconds 实际使用的 ΔT / ΔT actually used.
|
||||
DeltaTSeconds float64
|
||||
// Kind 本次计算的阴影类型 / shadow kind of this computation.
|
||||
Kind SolarEclipseShadowKind
|
||||
// Closed 边界由阴影自身闭合,false 表示被地平线切断 / self-closed or horizon-cut.
|
||||
Closed bool
|
||||
// Boundaries 物理边界分段,反经线会拆分 / physical boundary segments.
|
||||
Boundaries [][]SolarEclipsePathPoint
|
||||
// HorizonEnds 两端在地平圈上的擦地点,顺序与 Boundaries 一致 / horizon grazing points.
|
||||
HorizonEnds []SolarEclipsePathPoint
|
||||
// Topology 插值判定用的拓扑签名 / interpolation signature.
|
||||
Topology SolarEclipseShadowTopology
|
||||
}
|
||||
|
||||
// Empty 该时刻阴影未落在地球表面 / no footprint on the Earth.
|
||||
func (instant SolarEclipseShadowInstant) Empty() bool {
|
||||
return len(instant.Boundaries) == 0
|
||||
}
|
||||
|
||||
// SolarEclipseShadowSolver 可复用的单时刻求解器;非并发安全,宿主每条 lane 各持一个 / reusable solver, one per lane.
|
||||
type SolarEclipseShadowSolver struct {
|
||||
options SolarEclipseShadowSolverOptions
|
||||
|
||||
anchorSet bool
|
||||
anchorJDE float64
|
||||
anchorSolver solarEclipseSolver
|
||||
}
|
||||
|
||||
// NewSolarEclipseShadowSolver 构造单时刻求解器 / builds a single-instant solver.
|
||||
func NewSolarEclipseShadowSolver(options SolarEclipseShadowSolverOptions) *SolarEclipseShadowSolver {
|
||||
if options.Model != SolarEclipseModelIAUSingleK {
|
||||
options.Model = SolarEclipseModelNASABulletinSplitK
|
||||
}
|
||||
if options.BoundaryPoints <= 0 {
|
||||
options.BoundaryPoints = solarEclipseShadowDefaultBoundaryPoints
|
||||
}
|
||||
// 下界与整包采样同口径,个位数采样点会产出无意义的足迹。
|
||||
if options.BoundaryPoints < solarEclipsePartialFootprintMinBoundaryPoints {
|
||||
options.BoundaryPoints = solarEclipsePartialFootprintMinBoundaryPoints
|
||||
}
|
||||
if options.BoundaryPoints > solarEclipseShadowMaximumBoundaryPoints {
|
||||
options.BoundaryPoints = solarEclipseShadowMaximumBoundaryPoints
|
||||
}
|
||||
if options.Kind != SolarEclipseShadowPenumbra {
|
||||
options.Kind = SolarEclipseShadowUmbra
|
||||
}
|
||||
if options.TargetSpacingKM == 0 && options.Kind == SolarEclipseShadowPenumbra {
|
||||
// 半影的整包采样默认带空间加密,单时刻要用同一默认值才能与采样逐点一致。
|
||||
options.TargetSpacingKM = solarEclipsePartialFootprintTargetSpacingKM
|
||||
}
|
||||
if options.TargetSpacingKM < 0 {
|
||||
options.TargetSpacingKM = 0
|
||||
}
|
||||
return &SolarEclipseShadowSolver{options: options}
|
||||
}
|
||||
|
||||
// ShadowAtJDE 给定 TT 时刻的阴影足迹;不在地球上时返回 (零值, false) / footprint at one TT instant.
|
||||
func (solver *SolarEclipseShadowSolver) ShadowAtJDE(jdeTT float64) (SolarEclipseShadowInstant, bool) {
|
||||
if !finite(jdeTT) {
|
||||
return SolarEclipseShadowInstant{}, false
|
||||
}
|
||||
deltaT := solver.effectiveDeltaT(jdeTT)
|
||||
inner := solver.solverFor(jdeTT)
|
||||
moon, axis, sun := solver.besselGeometryAt(jdeTT, inner, deltaT)
|
||||
footprint := inner.shadowFootprintAtWithGeometry(
|
||||
jdeTT, moon, axis, sun, solver.options.BoundaryPoints,
|
||||
solver.options.shadowKind(), solver.options.TargetSpacingKM,
|
||||
)
|
||||
if len(footprint.Boundaries) == 0 {
|
||||
return SolarEclipseShadowInstant{
|
||||
JDE: jdeTT, Model: solver.options.Model,
|
||||
Kind: solver.options.Kind, DeltaTSeconds: deltaT,
|
||||
}, false
|
||||
}
|
||||
return SolarEclipseShadowInstant{
|
||||
JDE: jdeTT,
|
||||
Model: solver.options.Model,
|
||||
Kind: solver.options.Kind,
|
||||
DeltaTSeconds: deltaT,
|
||||
Closed: footprint.Closed,
|
||||
Boundaries: footprint.Boundaries,
|
||||
HorizonEnds: footprint.HorizonEnds,
|
||||
Topology: solarEclipseShadowFootprintTopology(footprint, solver.options.Kind),
|
||||
}, true
|
||||
}
|
||||
|
||||
func (options SolarEclipseShadowSolverOptions) shadowKind() solarEclipseShadowKind {
|
||||
if options.Kind == SolarEclipseShadowPenumbra {
|
||||
return solarEclipsePenumbralShadow
|
||||
}
|
||||
return solarEclipseCentralShadow
|
||||
}
|
||||
|
||||
func (solver *SolarEclipseShadowSolver) effectiveDeltaT(jdeTT float64) float64 {
|
||||
override := solver.options.DeltaTSeconds
|
||||
if override <= 0 {
|
||||
// 选项 0/负值表示"未覆盖",用模型;显式 ΔT=0 需走 DeltaTSecondsAt 的直接调用。
|
||||
override = math.NaN()
|
||||
}
|
||||
return DeltaTSecondsAt(jdeTT, override)
|
||||
}
|
||||
|
||||
func (solver *SolarEclipseShadowSolver) solverFor(jdeTT float64) solarEclipseSolver {
|
||||
if solver.anchorSet && math.Abs(jdeTT-solver.anchorJDE) <= solarEclipseShadowAnchorHalfWindowDays {
|
||||
return solver.anchorSolver
|
||||
}
|
||||
anchor := CalcMoonSHByJDE(jdeTT, 0)
|
||||
solver.anchorSet = true
|
||||
solver.anchorJDE = anchor
|
||||
solver.anchorSolver = newSolarEclipseSolver(anchor, solver.options.Model)
|
||||
return solver.anchorSolver
|
||||
}
|
||||
|
||||
func (solver *SolarEclipseShadowSolver) besselGeometryAt(
|
||||
jdeTT float64,
|
||||
inner solarEclipseSolver,
|
||||
deltaTSeconds float64,
|
||||
) ([3]float64, solarEclipseAxis, [3]float64) {
|
||||
if solver.options.DeltaTSeconds <= 0 {
|
||||
return inner.besselGeometryAt(jdeTT)
|
||||
}
|
||||
sunEquatorial, moonEquatorial := solarEclipseSunMoonEquatorial(jdeTT)
|
||||
axis := solarEclipseBesselAxisFromEquatorialWithDeltaT(
|
||||
jdeTT, sunEquatorial, moonEquatorial, deltaTSeconds,
|
||||
)
|
||||
return solarEclipseBesselMoonFromEquatorial(moonEquatorial, axis), axis, sunEquatorial
|
||||
}
|
||||
|
||||
// SolarEclipseShadowAtJDE 是无状态版本:自建一次内部状态后求单时刻足迹,任意并发安全。
|
||||
// SolarEclipseShadowAtJDE is the stateless variant: it builds its own state per call and
|
||||
// is therefore safe for concurrent use.
|
||||
func SolarEclipseShadowAtJDE(
|
||||
jdeTT float64,
|
||||
options SolarEclipseShadowSolverOptions,
|
||||
) (SolarEclipseShadowInstant, bool) {
|
||||
return NewSolarEclipseShadowSolver(options).ShadowAtJDE(jdeTT)
|
||||
}
|
||||
|
||||
func solarEclipseShadowFootprintTopology(
|
||||
footprint SolarEclipsePartialFootprint,
|
||||
kind SolarEclipseShadowKind,
|
||||
) SolarEclipseShadowTopology {
|
||||
topology := SolarEclipseShadowTopology{
|
||||
Kind: kind,
|
||||
Segments: len(footprint.Boundaries),
|
||||
Closed: footprint.Closed,
|
||||
}
|
||||
winding := 0.0
|
||||
for _, segment := range footprint.Boundaries {
|
||||
topology.Vertices += len(segment)
|
||||
for index := 1; index < len(segment); index++ {
|
||||
winding += math.Remainder(segment[index].Longitude-segment[index-1].Longitude, 360)
|
||||
}
|
||||
// 只有真正闭合的分段才把"末点回到首点"计入绕极判定;开放分段的收口边是虚拟的。
|
||||
if len(segment) > 2 && solarEclipsePathDistanceKM(segment[0], segment[len(segment)-1]) < 1e-6 {
|
||||
winding += math.Remainder(segment[0].Longitude-segment[len(segment)-1].Longitude, 360)
|
||||
}
|
||||
}
|
||||
if topology.Segments == 1 && math.Abs(winding) >= 180 {
|
||||
topology.EnclosesPole = true
|
||||
}
|
||||
return topology
|
||||
}
|
||||
|
||||
// SolarEclipseStationState 某瞬时的站心日月几何 / topocentric geometry at one instant.
|
||||
type SolarEclipseStationState struct {
|
||||
// JDE 输入的 TT 时刻,DeltaTSeconds 实际使用的 ΔT / requested instant and ΔT used.
|
||||
JDE float64
|
||||
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
|
||||
// InCentralPhase 该瞬时站点位于本影或反本影内 / station inside the central shadow now.
|
||||
InCentralPhase bool
|
||||
// CentralPhaseType 中心食类型,非中心食为 SolarEclipseNone / central phase kind.
|
||||
CentralPhaseType SolarEclipseType
|
||||
// HasTotalPhase 与 HasAnnularPhase 表示该瞬时是否处于全食或环食 / total or annular now.
|
||||
HasTotalPhase bool
|
||||
HasAnnularPhase bool
|
||||
// Visible 太阳中心高于几何地平,海拔用俯仰角修正阈值 / Sun center above the horizon.
|
||||
Visible bool
|
||||
}
|
||||
|
||||
// StationStateAtJDE 给定 TT 时刻与站点的站心情形,任何时刻可调用且不报错 / topocentric state at one instant.
|
||||
func (solver *SolarEclipseShadowSolver) StationStateAtJDE(
|
||||
jdeTT, lonDeg, latDeg, heightMeters float64,
|
||||
) SolarEclipseStationState {
|
||||
if !finite(jdeTT) || !finite(lonDeg) || !finite(latDeg) {
|
||||
return SolarEclipseStationState{JDE: jdeTT}
|
||||
}
|
||||
deltaT := solver.effectiveDeltaT(jdeTT)
|
||||
heightKM := heightMeters / 1000
|
||||
state := localSolarEclipseStateAtWithDeltaT(
|
||||
jdeTT, deltaT, lonDeg*rad, latDeg*rad, heightKM, solarEclipseModelParams(solver.options.Model),
|
||||
)
|
||||
contact := state.movingDiskContactState()
|
||||
central := contact.internalContactGap() <= 0
|
||||
result := SolarEclipseStationState{
|
||||
JDE: jdeTT,
|
||||
DeltaTSeconds: deltaT,
|
||||
SeparationDeg: state.separationRad / rad,
|
||||
SeparationArcsec: state.separationRad / rad * 3600,
|
||||
SunRadiusDeg: state.sunRadiusRad / rad,
|
||||
MoonOuterRadiusDeg: state.moonOuterRadiusRad / rad,
|
||||
MoonInnerRadiusDeg: state.moonInnerRadiusRad / rad,
|
||||
SunAltitudeDeg: state.sunAltitudeRad / rad,
|
||||
SunAzimuthDeg: state.sunAzimuthRad / rad,
|
||||
Obscuration: localSolarEclipseObscuration(state.sunRadiusRad, state.moonOuterRadiusRad, state.separationRad),
|
||||
InCentralPhase: central,
|
||||
}
|
||||
visibleThreshold := 0.0
|
||||
if heightMeters > 0 {
|
||||
visibleThreshold = -HeightDegreeByLat(heightMeters, latDeg) * rad
|
||||
}
|
||||
result.Visible = state.sunAltitudeRad > visibleThreshold
|
||||
if central {
|
||||
result.Magnitude = state.moonInnerRadiusRad / state.sunRadiusRad
|
||||
if state.moonInnerRadiusRad >= state.sunRadiusRad {
|
||||
result.CentralPhaseType = SolarEclipseTotal
|
||||
result.HasTotalPhase = true
|
||||
} else {
|
||||
result.CentralPhaseType = SolarEclipseAnnular
|
||||
result.HasAnnularPhase = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
result.Magnitude = (state.moonOuterRadiusRad + state.sunRadiusRad - state.separationRad) / (2 * state.sunRadiusRad)
|
||||
if result.Magnitude < 0 {
|
||||
result.Magnitude = 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// SolarEclipseStationStateAtJDE 无状态版本,可并发调用 / stateless, concurrency-safe variant.
|
||||
func SolarEclipseStationStateAtJDE(
|
||||
jdeTT, lonDeg, latDeg, heightMeters float64,
|
||||
options SolarEclipseShadowSolverOptions,
|
||||
) SolarEclipseStationState {
|
||||
return NewSolarEclipseShadowSolver(options).StationStateAtJDE(jdeTT, lonDeg, latDeg, heightMeters)
|
||||
}
|
||||
|
||||
func localSolarEclipseStateAtWithDeltaT(
|
||||
jdTT, deltaTSeconds, lonRad, latRad, heightKM float64,
|
||||
params solarEclipseModelParameters,
|
||||
) localSolarEclipseState {
|
||||
context := newLocalSolarEclipseStateContextWithDeltaT(jdTT, deltaTSeconds, params)
|
||||
return context.stateAt(lonRad, latRad, heightKM)
|
||||
}
|
||||
|
||||
func newLocalSolarEclipseStateContextWithDeltaT(
|
||||
jdTT, deltaTSeconds float64,
|
||||
params solarEclipseModelParameters,
|
||||
) localSolarEclipseStateContext {
|
||||
sunEquatorial, moonEquatorial := solarEclipseSunMoonEquatorial(jdTT)
|
||||
utJDE := jdTT - deltaTSeconds/86400
|
||||
return localSolarEclipseStateContext{
|
||||
sunXYZ: solarEclipseLLRToXYZ(sunEquatorial[0], sunEquatorial[1], sunEquatorial[2]),
|
||||
moonXYZ: solarEclipseLLRToXYZ(moonEquatorial[0], moonEquatorial[1], moonEquatorial[2]),
|
||||
gst: ApparentSiderealTime(utJDE) * 15 * rad,
|
||||
params: params,
|
||||
}
|
||||
}
|
||||
|
||||
// ShadowBetweenJDE 区间内等步长逐时刻的阴影足迹,无阴影时刻为空条目 / per-step footprints.
|
||||
func (solver *SolarEclipseShadowSolver) ShadowBetweenJDE(
|
||||
startJDE, endJDE, stepDays float64,
|
||||
) []SolarEclipseShadowInstant {
|
||||
if !finite(startJDE) || !finite(endJDE) || !finite(stepDays) || stepDays <= 0 || endJDE < startJDE {
|
||||
return nil
|
||||
}
|
||||
count := solarEclipseShadowBatchCount(startJDE, endJDE, stepDays)
|
||||
if count <= 0 || count > solarEclipseShadowMaximumBatchCount {
|
||||
return nil
|
||||
}
|
||||
result := make([]SolarEclipseShadowInstant, 0, count)
|
||||
for index := 0; index < count; index++ {
|
||||
instant, _ := solver.ShadowAtJDE(startJDE + float64(index)*stepDays)
|
||||
result = append(result, instant)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// StationStatesBetweenJDE 区间内等步长逐时刻的站心情形 / per-step station states.
|
||||
func (solver *SolarEclipseShadowSolver) StationStatesBetweenJDE(
|
||||
startJDE, endJDE, stepDays, lonDeg, latDeg, heightMeters float64,
|
||||
) []SolarEclipseStationState {
|
||||
if !finite(startJDE) || !finite(endJDE) || !finite(stepDays) || stepDays <= 0 || endJDE < startJDE {
|
||||
return nil
|
||||
}
|
||||
count := solarEclipseShadowBatchCount(startJDE, endJDE, stepDays)
|
||||
if count <= 0 || count > solarEclipseShadowMaximumBatchCount {
|
||||
return nil
|
||||
}
|
||||
result := make([]SolarEclipseStationState, 0, count)
|
||||
for index := 0; index < count; index++ {
|
||||
result = append(result, solver.StationStateAtJDE(
|
||||
startJDE+float64(index)*stepDays, lonDeg, latDeg, heightMeters,
|
||||
))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// solarEclipseShadowBatchTimeTolerance 是批量采样末点的时刻容差(天,约 0.9 ms):
|
||||
// 起止点由浮点运算给出时末点会落在 end 之外若干个 ULP,容差内仍算入,避免丢掉本应包含的采样。
|
||||
const solarEclipseShadowBatchTimeTolerance = 1e-8
|
||||
|
||||
// solarEclipseShadowBatchCount 返回闭区间上按 stepDays 采样的格点数。
|
||||
func solarEclipseShadowBatchCount(startJDE, endJDE, stepDays float64) int {
|
||||
return int(math.Floor((endJDE-startJDE+solarEclipseShadowBatchTimeTolerance)/stepDays)) + 1
|
||||
}
|
||||
|
||||
// SolarEclipseShadowBetweenJDE 无状态批量版本 / stateless batch variant.
|
||||
func SolarEclipseShadowBetweenJDE(
|
||||
startJDE, endJDE, stepDays float64, options SolarEclipseShadowSolverOptions,
|
||||
) []SolarEclipseShadowInstant {
|
||||
return NewSolarEclipseShadowSolver(options).ShadowBetweenJDE(startJDE, endJDE, stepDays)
|
||||
}
|
||||
Reference in New Issue
Block a user