044f2f4381
- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算 - 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询 - 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出 - 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解 - 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤 - 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持 - 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理 - 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
566 lines
20 KiB
Go
566 lines
20 KiB
Go
package orbit
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"sync"
|
|
|
|
"b612.me/astro/basic"
|
|
"b612.me/astro/internal/ephem"
|
|
)
|
|
|
|
const (
|
|
propagationAU = 149597870.7
|
|
propagationGaussianK = 0.01720209895
|
|
propagationSunGM = propagationGaussianK * propagationGaussianK * propagationAU * propagationAU * propagationAU
|
|
)
|
|
|
|
var (
|
|
// ErrInvalidPropagationElements 根数不能用于传播 / orbital elements cannot be propagated.
|
|
ErrInvalidPropagationElements = errors.New("orbit: invalid propagation elements")
|
|
// ErrPerturbationEngine 摄动传播缺少天体星历 / perturbation propagation requires an ephemeris.
|
|
ErrPerturbationEngine = errors.New("orbit: perturbation ephemeris is not configured")
|
|
// ErrUnsupportedPerturber 摄动天体未知或重复计入 / perturbing body is unknown or counted twice.
|
|
ErrUnsupportedPerturber = errors.New("orbit: unsupported perturbing body")
|
|
)
|
|
|
|
// maxPropagationNodes 单次传播允许的网格步数上限 / upper bound on grid steps of one propagation.
|
|
const maxPropagationNodes = 1 << 30
|
|
|
|
// Body NAIF 天体编号 / NAIF body identifier.
|
|
type Body = ephem.Body
|
|
|
|
// StateProvider 提供摄动天体状态 / supplies perturbing-body states.
|
|
type StateProvider = ephem.Engine
|
|
|
|
// PropagationOptions 可选摄动传播参数;零值保持纯二体 / optional perturbation propagation parameters; zero preserves two-body propagation.
|
|
type PropagationOptions struct {
|
|
Perturbers []Body // 摄动天体 / perturbing bodies.
|
|
Engine StateProvider // 摄动星历 / perturbation ephemeris.
|
|
Context StateProvider // 摄动星历别名 / alternate perturbation ephemeris.
|
|
Step float64 // 最大步长,单位日 / maximum step in days.
|
|
Tolerance float64 // 步长加倍的相对误差阈值 / relative step-doubling error threshold.
|
|
}
|
|
|
|
// State 计算日心 J2000 平黄道根数的二体状态,输出 ICRF km 与 km/日 / computes a two-body state from heliocentric J2000 ecliptic elements, returning ICRF km and km/day.
|
|
func State(elements Elements, jdTDB float64) (pos, vel [3]float64, err error) {
|
|
return twoBodyState(elements, jdTDB)
|
|
}
|
|
|
|
// StateWithOptions 按选项传播根数;零值与 State 完全一致 / propagates elements with optional perturbations; zero options exactly match State.
|
|
func StateWithOptions(elements Elements, jdTDB float64, options PropagationOptions) (pos, vel [3]float64, err error) {
|
|
if !finite(jdTDB) {
|
|
return pos, vel, ErrInvalidPropagationElements
|
|
}
|
|
perturbers, err := normalizePerturbers(options.Perturbers)
|
|
if err != nil {
|
|
return pos, vel, err
|
|
}
|
|
if len(perturbers) == 0 {
|
|
return twoBodyState(elements, jdTDB)
|
|
}
|
|
run, err := newPropagationRun(elements, perturbers, options)
|
|
if err != nil {
|
|
return pos, vel, err
|
|
}
|
|
return run.state(jdTDB, nil)
|
|
}
|
|
|
|
// propagationRun 固定一次传播的根数、天体与步长网格。
|
|
type propagationRun struct {
|
|
elements Elements
|
|
perturbers []Body
|
|
provider StateProvider
|
|
step float64
|
|
tolerance float64
|
|
epoch float64
|
|
start propagationCheckpoint
|
|
}
|
|
|
|
type propagationCheckpoint struct {
|
|
pos [3]float64
|
|
vel [3]float64
|
|
}
|
|
|
|
// propagationCheckpointStride 每隔多少个网格点缓存一次状态。
|
|
const propagationCheckpointStride = 32
|
|
|
|
// propagationCheckpoints 引擎缓存的网格状态,下标 j 对应 epoch±j·stride·step。
|
|
type propagationCheckpoints struct {
|
|
forward []propagationCheckpoint
|
|
backward []propagationCheckpoint
|
|
}
|
|
|
|
func newPropagationRun(elements Elements, perturbers []Body, options PropagationOptions) (*propagationRun, error) {
|
|
provider := options.Engine
|
|
if provider == nil {
|
|
provider = options.Context
|
|
}
|
|
if provider == nil {
|
|
return nil, ErrPerturbationEngine
|
|
}
|
|
epoch := propagationEpoch(elements)
|
|
// 经典根数漏填 EpochJD 时为 0,会从 JD 0 起积分数千年,按非法根数拒绝。
|
|
if !finite(epoch) || epoch == 0 {
|
|
return nil, ErrInvalidPropagationElements
|
|
}
|
|
pos, vel, err := twoBodyState(elements, epoch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
step := options.Step
|
|
if !(step > 0) || !finite(step) {
|
|
step = 0.5
|
|
}
|
|
tolerance := options.Tolerance
|
|
if !(tolerance > 0) || !finite(tolerance) {
|
|
tolerance = 1e-6
|
|
}
|
|
return &propagationRun{
|
|
elements: elements,
|
|
perturbers: perturbers,
|
|
provider: provider,
|
|
step: step,
|
|
tolerance: tolerance,
|
|
epoch: epoch,
|
|
start: propagationCheckpoint{pos: pos, vel: vel},
|
|
}, nil
|
|
}
|
|
|
|
// propagationEpoch 积分起点:近日点形式给出 EpochJD 时取其为密切历元,否则用 TpJD / integration start: the osculating EpochJD when a perihelion-form set provides one, otherwise TpJD.
|
|
func propagationEpoch(elements Elements) float64 {
|
|
if usesPerihelionForm(elements) && (elements.EpochJD == 0 || !finite(elements.EpochJD)) {
|
|
return elements.TpJD
|
|
}
|
|
return elements.EpochJD
|
|
}
|
|
|
|
// state 先沿固定网格积分到不越过目标的最后一个格点,再积分余量;结果只取决于目标历元,与 cache 内容无关 / integrates along a fixed grid to the last node short of the target, then the remainder; the result depends only on the target, never on cache contents.
|
|
func (run *propagationRun) state(target float64, cache *propagationCheckpoints) (pos, vel [3]float64, err error) {
|
|
if target == run.epoch {
|
|
return run.start.pos, run.start.vel, nil
|
|
}
|
|
direction := 1.0
|
|
if target < run.epoch {
|
|
direction = -1
|
|
}
|
|
span := math.Abs(target - run.epoch)
|
|
count := math.Floor(span / run.step)
|
|
if count > maxPropagationNodes {
|
|
return pos, vel, ErrInvalidPropagationElements
|
|
}
|
|
index := int(count)
|
|
for index > 0 && math.Abs(run.node(index, direction)-run.epoch) > span {
|
|
index--
|
|
}
|
|
current, known := run.start, 0
|
|
var stored *[]propagationCheckpoint
|
|
if cache != nil {
|
|
stored = &cache.forward
|
|
if direction < 0 {
|
|
stored = &cache.backward
|
|
}
|
|
if len(*stored) == 0 {
|
|
*stored = append(*stored, run.start)
|
|
}
|
|
slot := index / propagationCheckpointStride
|
|
if slot > len(*stored)-1 {
|
|
slot = len(*stored) - 1
|
|
}
|
|
known = slot * propagationCheckpointStride
|
|
current = (*stored)[slot]
|
|
}
|
|
session := newPropagationSession(run.provider)
|
|
for k := known; k < index; k++ {
|
|
current.pos, current.vel, err = run.integrate(current.pos, current.vel, run.node(k, direction), run.node(k+1, direction), session)
|
|
if err != nil {
|
|
return current.pos, current.vel, err
|
|
}
|
|
if stored != nil && (k+1)%propagationCheckpointStride == 0 && len(*stored) == (k+1)/propagationCheckpointStride {
|
|
*stored = append(*stored, current)
|
|
}
|
|
}
|
|
from := run.node(index, direction)
|
|
if from == target {
|
|
return current.pos, current.vel, nil
|
|
}
|
|
return run.integrate(current.pos, current.vel, from, target, session)
|
|
}
|
|
|
|
func (run *propagationRun) node(index int, direction float64) float64 {
|
|
return run.epoch + direction*float64(index)*run.step
|
|
}
|
|
|
|
// integrate 自适应子步积分 [from, to],终点精确落在 to / adaptively integrates [from, to], landing exactly on to.
|
|
func (run *propagationRun) integrate(pos, vel [3]float64, from, to float64, session *propagationSession) (outPos, outVel [3]float64, err error) {
|
|
outPos, outVel = pos, vel
|
|
current := from
|
|
for current != to {
|
|
dt := to - current
|
|
var used float64
|
|
outPos, outVel, used, err = propagationAdaptiveStep(outPos, outVel, current, dt, run.elements, run.perturbers, session, run.tolerance)
|
|
if err != nil {
|
|
return outPos, outVel, err
|
|
}
|
|
if used == dt {
|
|
current = to
|
|
} else {
|
|
current += used
|
|
}
|
|
}
|
|
return outPos, outVel, nil
|
|
}
|
|
|
|
func propagationAdaptiveStep(pos, vel [3]float64, jd, dt float64, elements Elements, perturbers []Body, session *propagationSession, tolerance float64) (outPos, outVel [3]float64, used float64, err error) {
|
|
trial := dt
|
|
lastError := math.Inf(1)
|
|
for attempts := 0; attempts < 20; attempts++ {
|
|
fullPos, fullVel, fullErr := propagationRK4(pos, vel, jd, trial, elements, perturbers, session)
|
|
if fullErr != nil {
|
|
return pos, vel, trial, fullErr
|
|
}
|
|
halfPos, halfVel, halfErr := propagationRK4(pos, vel, jd, trial/2, elements, perturbers, session)
|
|
if halfErr != nil {
|
|
return pos, vel, trial, halfErr
|
|
}
|
|
halfPos, halfVel, halfErr = propagationRK4(halfPos, halfVel, jd+trial/2, trial/2, elements, perturbers, session)
|
|
if halfErr != nil {
|
|
return pos, vel, trial, halfErr
|
|
}
|
|
error := 0.0
|
|
for index := range fullPos {
|
|
positionScale := math.Max(1, math.Max(math.Abs(fullPos[index]), math.Abs(halfPos[index])))
|
|
velocityScale := math.Max(1, math.Max(math.Abs(fullVel[index]), math.Abs(halfVel[index])))
|
|
error = math.Max(error, math.Abs(fullPos[index]-halfPos[index])/(15*positionScale))
|
|
error = math.Max(error, math.Abs(fullVel[index]-halfVel[index])/(15*velocityScale))
|
|
}
|
|
lastError = error
|
|
if error <= tolerance || math.Abs(trial) <= 1e-8 {
|
|
return halfPos, halfVel, trial, nil
|
|
}
|
|
trial /= 2
|
|
}
|
|
return pos, vel, trial, fmt.Errorf("orbit: propagation step did not converge (dt=%g error=%g tolerance=%g)", trial, lastError, tolerance)
|
|
}
|
|
|
|
type propagationSession struct {
|
|
provider StateProvider
|
|
states map[propagationStateKey]propagationSample
|
|
}
|
|
|
|
type propagationStateKey struct {
|
|
body Body
|
|
jd uint64
|
|
}
|
|
|
|
type propagationSample struct {
|
|
pos [3]float64
|
|
vel [3]float64
|
|
err error
|
|
}
|
|
|
|
func newPropagationSession(provider StateProvider) *propagationSession {
|
|
return &propagationSession{
|
|
provider: provider,
|
|
states: make(map[propagationStateKey]propagationSample),
|
|
}
|
|
}
|
|
|
|
func (session *propagationSession) state(body Body, jd float64) (pos, vel [3]float64, err error) {
|
|
key := propagationStateKey{body: body, jd: math.Float64bits(jd)}
|
|
if sample, ok := session.states[key]; ok {
|
|
return sample.pos, sample.vel, sample.err
|
|
}
|
|
pos, vel, err = session.provider.State(body, ephem.SSB, jd)
|
|
session.states[key] = propagationSample{pos: pos, vel: vel, err: err}
|
|
return pos, vel, err
|
|
}
|
|
|
|
// EphemerisEngine 根数传播的只读星历引擎,结果与 StateWithOptions 逐位一致且不受查询顺序影响 / read-only engine over propagated elements.
|
|
type EphemerisEngine struct {
|
|
target Body
|
|
elements Elements
|
|
options PropagationOptions
|
|
setupErr error
|
|
run *propagationRun
|
|
mu sync.Mutex
|
|
cache propagationCheckpoints
|
|
}
|
|
|
|
// NewEphemerisEngine 创建根数传播星历引擎;选项无效时 State 返回对应错误 / creates an ephemeris engine backed by propagated elements; invalid options surface from State.
|
|
func NewEphemerisEngine(target Body, elements Elements, options PropagationOptions) *EphemerisEngine {
|
|
engine := &EphemerisEngine{target: target, elements: elements}
|
|
options.Perturbers, engine.setupErr = normalizePerturbers(options.Perturbers)
|
|
engine.options = options
|
|
if engine.setupErr == nil && len(options.Perturbers) != 0 {
|
|
engine.run, engine.setupErr = newPropagationRun(elements, options.Perturbers, options)
|
|
}
|
|
return engine
|
|
}
|
|
|
|
// Target 返回传播目标 / returns the propagated target.
|
|
func (engine *EphemerisEngine) Target() Body { return engine.target }
|
|
|
|
// Elements 返回传播根数 / returns the propagation elements.
|
|
func (engine *EphemerisEngine) Elements() Elements { return engine.elements }
|
|
|
|
// State 返回传播目标相对指定中心的状态 / returns propagated target state relative to a center.
|
|
func (engine *EphemerisEngine) State(target, center Body, jdTDB float64) (pos, vel [3]float64, err error) {
|
|
if engine == nil || target != engine.target {
|
|
if target == center {
|
|
return pos, vel, nil
|
|
}
|
|
return pos, vel, ephem.ErrUnsupportedBody
|
|
}
|
|
if target == center {
|
|
return pos, vel, nil
|
|
}
|
|
if center != ephem.Sun && center != ephem.SSB {
|
|
return pos, vel, ephem.ErrUnsupportedBody
|
|
}
|
|
if engine.setupErr != nil {
|
|
return pos, vel, engine.setupErr
|
|
}
|
|
if !finite(jdTDB) {
|
|
return pos, vel, ErrInvalidPropagationElements
|
|
}
|
|
if engine.run == nil {
|
|
pos, vel, err = twoBodyState(engine.elements, jdTDB)
|
|
} else {
|
|
engine.mu.Lock()
|
|
pos, vel, err = engine.run.state(jdTDB, &engine.cache)
|
|
engine.mu.Unlock()
|
|
}
|
|
if err != nil {
|
|
return pos, vel, err
|
|
}
|
|
if center == ephem.Sun {
|
|
return pos, vel, nil
|
|
}
|
|
provider := engine.options.Engine
|
|
if provider == nil {
|
|
provider = engine.options.Context
|
|
}
|
|
if provider == nil {
|
|
return pos, vel, ErrPerturbationEngine
|
|
}
|
|
sunPos, sunVel, sunErr := provider.State(ephem.Sun, ephem.SSB, jdTDB)
|
|
if sunErr != nil {
|
|
return pos, vel, sunErr
|
|
}
|
|
for index := range pos {
|
|
pos[index] += sunPos[index]
|
|
vel[index] += sunVel[index]
|
|
}
|
|
return pos, vel, nil
|
|
}
|
|
|
|
// Coverage 返回传播引擎的数学覆盖范围 / returns the mathematical coverage of the propagator.
|
|
func (engine *EphemerisEngine) Coverage(target Body) (start, end float64, ok bool) {
|
|
if engine == nil || target != engine.target {
|
|
return 0, 0, false
|
|
}
|
|
return math.Inf(-1), math.Inf(1), true
|
|
}
|
|
|
|
func propagationRK4(pos, vel [3]float64, jd, dt float64, elements Elements, perturbers []Body, session *propagationSession) ([3]float64, [3]float64, error) {
|
|
p1, v1, err := propagationDerivative(pos, vel, jd, elements, perturbers, session)
|
|
if err != nil {
|
|
return pos, vel, err
|
|
}
|
|
p2 := vectorAdd(pos, vectorScale(p1, dt/2))
|
|
v2 := vectorAdd(vel, vectorScale(v1, dt/2))
|
|
p2d, v2d, err := propagationDerivative(p2, v2, jd+dt/2, elements, perturbers, session)
|
|
if err != nil {
|
|
return pos, vel, err
|
|
}
|
|
p3 := vectorAdd(pos, vectorScale(p2d, dt/2))
|
|
v3 := vectorAdd(vel, vectorScale(v2d, dt/2))
|
|
p3d, v3d, err := propagationDerivative(p3, v3, jd+dt/2, elements, perturbers, session)
|
|
if err != nil {
|
|
return pos, vel, err
|
|
}
|
|
p4 := vectorAdd(pos, vectorScale(p3d, dt))
|
|
v4 := vectorAdd(vel, vectorScale(v3d, dt))
|
|
p4d, v4d, err := propagationDerivative(p4, v4, jd+dt, elements, perturbers, session)
|
|
if err != nil {
|
|
return pos, vel, err
|
|
}
|
|
return vectorAdd(pos, vectorScale(vectorAdd(vectorAdd(p1, vectorScale(p2d, 2)), vectorAdd(vectorScale(p3d, 2), p4d)), dt/6)), vectorAdd(vel, vectorScale(vectorAdd(vectorAdd(v1, vectorScale(v2d, 2)), vectorAdd(vectorScale(v3d, 2), v4d)), dt/6)), nil
|
|
}
|
|
|
|
func propagationDerivative(pos, vel [3]float64, jd float64, elements Elements, perturbers []Body, session *propagationSession) ([3]float64, [3]float64, error) {
|
|
radius := vectorNorm(pos)
|
|
if !(radius > 0) || !finite(radius) {
|
|
return vel, [3]float64{}, ErrInvalidPropagationElements
|
|
}
|
|
acc := vectorScale(pos, -propagationSunGM/math.Pow(radius, 3))
|
|
sunPos, _, err := session.state(ephem.Sun, jd)
|
|
if err != nil {
|
|
return vel, acc, err
|
|
}
|
|
for _, body := range perturbers {
|
|
if body == ephem.Sun || body == ephem.SSB {
|
|
continue
|
|
}
|
|
bodyPos, _, bodyErr := session.state(body, jd)
|
|
if bodyErr != nil {
|
|
return vel, acc, bodyErr
|
|
}
|
|
relBody := vectorSub(bodyPos, sunPos)
|
|
toBody := vectorSub(relBody, pos)
|
|
mu := perturbingGM(body)
|
|
if vectorNorm(relBody) == 0 || vectorNorm(toBody) == 0 {
|
|
return vel, acc, ErrInvalidPropagationElements
|
|
}
|
|
acc = vectorAdd(acc, vectorScale(toBody, mu/math.Pow(vectorNorm(toBody), 3)))
|
|
acc = vectorAdd(acc, vectorScale(relBody, -mu/math.Pow(vectorNorm(relBody), 3)))
|
|
}
|
|
return vel, acc, nil
|
|
}
|
|
|
|
func perturbingGM(body Body) float64 {
|
|
const secondPerDay = 86400.0
|
|
var gm float64
|
|
switch body {
|
|
case ephem.Mercury:
|
|
gm = 22031.86855
|
|
case ephem.Venus:
|
|
gm = 324858.592
|
|
case ephem.Earth:
|
|
gm = 398600.435436
|
|
case ephem.Moon:
|
|
gm = 4902.800066
|
|
case ephem.EMB:
|
|
gm = 398600.435436 + 4902.800066
|
|
case ephem.Mars:
|
|
gm = 42828.375214
|
|
case ephem.Jupiter:
|
|
gm = 126686534.911
|
|
case ephem.Saturn:
|
|
gm = 37931207.8
|
|
case ephem.Uranus:
|
|
gm = 5793966.0
|
|
case ephem.Neptune:
|
|
gm = 6835100.0
|
|
}
|
|
return gm * secondPerDay * secondPerDay
|
|
}
|
|
|
|
// normalizePerturbers 去重并跳过太阳与 SSB;未知天体或 EMB 与地球/月球同时出现时报错 / dedups and skips Sun and SSB; rejects unknown bodies and EMB combined with Earth or Moon.
|
|
func normalizePerturbers(perturbers []Body) ([]Body, error) {
|
|
if len(perturbers) == 0 {
|
|
return nil, nil
|
|
}
|
|
seen := make(map[Body]struct{}, len(perturbers))
|
|
result := make([]Body, 0, len(perturbers))
|
|
for _, body := range perturbers {
|
|
if body == ephem.Sun || body == ephem.SSB {
|
|
continue
|
|
}
|
|
if perturbingGM(body) == 0 {
|
|
return nil, fmt.Errorf("%w: %d", ErrUnsupportedPerturber, body)
|
|
}
|
|
if _, ok := seen[body]; ok {
|
|
continue
|
|
}
|
|
seen[body] = struct{}{}
|
|
result = append(result, body)
|
|
}
|
|
_, hasEMB := seen[ephem.EMB]
|
|
_, hasEarth := seen[ephem.Earth]
|
|
_, hasMoon := seen[ephem.Moon]
|
|
if hasEMB && (hasEarth || hasMoon) {
|
|
return nil, fmt.Errorf("%w: EMB already includes Earth and Moon", ErrUnsupportedPerturber)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func twoBodyState(elements Elements, jd float64) (pos, vel [3]float64, err error) {
|
|
if !finite(jd) {
|
|
return pos, vel, ErrInvalidPropagationElements
|
|
}
|
|
basicElements := toBasicElements(elements)
|
|
trueAnomaly := basic.OrbitTrueAnomaly(jd, basicElements)
|
|
if math.IsNaN(trueAnomaly) {
|
|
return pos, vel, ErrInvalidPropagationElements
|
|
}
|
|
resolved := elementsAt(elements, jd)
|
|
eccentricity := resolved.E
|
|
semiLatus := resolved.A * (1 - eccentricity*eccentricity)
|
|
if usesPerihelionForm(elements) {
|
|
semiLatus = resolved.Q * (1 + eccentricity)
|
|
}
|
|
if !(semiLatus > 0) || !finite(semiLatus) {
|
|
return pos, vel, ErrInvalidPropagationElements
|
|
}
|
|
nu := trueAnomaly * math.Pi / 180
|
|
mu := propagationGaussianK * propagationGaussianK
|
|
radius := semiLatus / (1 + eccentricity*math.Cos(nu))
|
|
if !(radius > 0) || !finite(radius) {
|
|
return pos, vel, ErrInvalidPropagationElements
|
|
}
|
|
x := radius * math.Cos(nu)
|
|
y := radius * math.Sin(nu)
|
|
speedScale := math.Sqrt(mu / semiLatus)
|
|
vx := -speedScale * math.Sin(nu)
|
|
vy := speedScale * (eccentricity + math.Cos(nu))
|
|
posAU, velAU := rotatePerifocal(x, y, vx, vy, resolved)
|
|
return rotateEquatorial(posAU, propagationAU), rotateEquatorial(velAU, propagationAU), nil
|
|
}
|
|
|
|
func elementsAt(elements Elements, jd float64) Elements {
|
|
if usesPerihelionForm(elements) || !finite(elements.EpochJD) {
|
|
return elements
|
|
}
|
|
delta := jd - elements.EpochJD
|
|
elements.A += elements.ADot * delta
|
|
elements.E += elements.EDot * delta
|
|
elements.I += elements.IDot * delta
|
|
elements.Omega += elements.OmegaDot * delta
|
|
elements.W += elements.WDot * delta
|
|
return elements
|
|
}
|
|
|
|
func usesPerihelionForm(elements Elements) bool { return elements.Q > 0 && finite(elements.TpJD) }
|
|
|
|
func rotatePerifocal(x, y, vx, vy float64, elements Elements) ([3]float64, [3]float64) {
|
|
omega := elements.Omega * math.Pi / 180
|
|
argument := elements.W * math.Pi / 180
|
|
inclination := elements.I * math.Pi / 180
|
|
sinOmega, cosOmega := math.Sincos(omega)
|
|
sinArgument, cosArgument := math.Sincos(argument)
|
|
sinInclination, cosInclination := math.Sincos(inclination)
|
|
basisX := [3]float64{cosOmega*cosArgument - sinOmega*sinArgument*cosInclination, sinOmega*cosArgument + cosOmega*sinArgument*cosInclination, sinArgument * sinInclination}
|
|
basisY := [3]float64{-cosOmega*sinArgument - sinOmega*cosArgument*cosInclination, -sinOmega*sinArgument + cosOmega*cosArgument*cosInclination, cosArgument * sinInclination}
|
|
return vectorAdd(vectorScale(basisX, x), vectorScale(basisY, y)), vectorAdd(vectorScale(basisX, vx), vectorScale(basisY, vy))
|
|
}
|
|
|
|
var sinJ2000Obliquity, cosJ2000Obliquity = math.Sincos(basic.EclipticObliquity(2451545.0, false) * math.Pi / 180)
|
|
|
|
func rotateEquatorial(value [3]float64, scale float64) [3]float64 {
|
|
sinEps, cosEps := sinJ2000Obliquity, cosJ2000Obliquity
|
|
return [3]float64{value[0] * scale, (value[1]*cosEps - value[2]*sinEps) * scale, (value[1]*sinEps + value[2]*cosEps) * scale}
|
|
}
|
|
|
|
func vectorAdd(left, right [3]float64) [3]float64 {
|
|
return [3]float64{left[0] + right[0], left[1] + right[1], left[2] + right[2]}
|
|
}
|
|
|
|
func vectorSub(left, right [3]float64) [3]float64 {
|
|
return [3]float64{left[0] - right[0], left[1] - right[1], left[2] - right[2]}
|
|
}
|
|
|
|
func vectorScale(value [3]float64, scale float64) [3]float64 {
|
|
return [3]float64{value[0] * scale, value[1] * scale, value[2] * scale}
|
|
}
|
|
|
|
func vectorNorm(value [3]float64) float64 {
|
|
return math.Sqrt(value[0]*value[0] + value[1]*value[1] + value[2]*value[2])
|
|
}
|
|
|
|
func finite(value float64) bool { return !math.IsNaN(value) && !math.IsInf(value, 0) }
|
|
|
|
var _ StateProvider = (*EphemerisEngine)(nil)
|