244 lines
6.3 KiB
Go
244 lines
6.3 KiB
Go
|
|
package ephem
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"sort"
|
||
|
|
)
|
||
|
|
|
||
|
|
// BodyProvider 可选地报告引擎能够触及的天体编号 / optionally reports bodies reachable by an engine.
|
||
|
|
type BodyProvider interface {
|
||
|
|
Bodies() []Body
|
||
|
|
}
|
||
|
|
|
||
|
|
// Stack 按顺序叠加多个只读星历引擎 / ordered stack of read-only ephemeris engines.
|
||
|
|
type Stack struct {
|
||
|
|
engines []Engine
|
||
|
|
// extra 为各源声明、且不在 chainBodies 中的天体,创建时算好。
|
||
|
|
extra []Body
|
||
|
|
}
|
||
|
|
|
||
|
|
// stackInlineBodies 中心链搜索在栈上预留的候选数,超出时 append 退回堆分配。
|
||
|
|
const stackInlineBodies = 24
|
||
|
|
|
||
|
|
type stackNode struct {
|
||
|
|
body Body
|
||
|
|
pos, vel [3]float64
|
||
|
|
}
|
||
|
|
|
||
|
|
// chainBodies 中心链搜索的固定候选,顺序决定同等路径的取舍。
|
||
|
|
var chainBodies = [...]Body{SSB, Sun, Mercury, Venus, EMB, Earth, Moon, Mars, Jupiter, Saturn, Uranus, Neptune}
|
||
|
|
|
||
|
|
// NewStack 创建多源星历;先传入的引擎优先 / creates a multi-source ephemeris; earlier engines have priority.
|
||
|
|
func NewStack(engines ...Engine) *Stack {
|
||
|
|
usable := make([]Engine, 0, len(engines))
|
||
|
|
for _, engine := range engines {
|
||
|
|
if !IsNilEngine(engine) {
|
||
|
|
usable = append(usable, engine)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
stack := &Stack{engines: usable}
|
||
|
|
for _, body := range stack.Bodies() {
|
||
|
|
if !containsBody(chainBodies[:], body) {
|
||
|
|
stack.extra = append(stack.extra, body)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return stack
|
||
|
|
}
|
||
|
|
|
||
|
|
// Engines 返回底层引擎快照 / returns a snapshot of the underlying engines.
|
||
|
|
func (stack *Stack) Engines() []Engine {
|
||
|
|
if stack == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return append([]Engine(nil), stack.engines...)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Bodies 返回各源声明的天体编号 / returns body identifiers declared by the sources.
|
||
|
|
func (stack *Stack) Bodies() []Body {
|
||
|
|
if stack == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
seen := make(map[Body]bool)
|
||
|
|
result := make([]Body, 0)
|
||
|
|
for _, engine := range stack.engines {
|
||
|
|
provider, ok := engine.(BodyProvider)
|
||
|
|
if !ok {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
for _, body := range provider.Bodies() {
|
||
|
|
if !seen[body] {
|
||
|
|
seen[body] = true
|
||
|
|
result = append(result, body)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sort.Slice(result, func(left, right int) bool { return result[left] < result[right] })
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
// Coverage 返回所有源覆盖区间的包络 / returns the envelope of all source coverage intervals.
|
||
|
|
func (stack *Stack) Coverage(target Body) (start, end float64, ok bool) {
|
||
|
|
if stack == nil {
|
||
|
|
return 0, 0, false
|
||
|
|
}
|
||
|
|
for _, engine := range stack.engines {
|
||
|
|
engineStart, engineEnd, engineOK := engine.Coverage(target)
|
||
|
|
if !engineOK {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if !ok || engineStart < start {
|
||
|
|
start = engineStart
|
||
|
|
}
|
||
|
|
if !ok || engineEnd > end {
|
||
|
|
end = engineEnd
|
||
|
|
}
|
||
|
|
ok = true
|
||
|
|
}
|
||
|
|
return start, end, ok
|
||
|
|
}
|
||
|
|
|
||
|
|
// State 返回目标相对中心的状态,并在源之间拼接中心链 / returns target-relative state, composing center chains across sources.
|
||
|
|
func (stack *Stack) State(target, center Body, jdTDB float64) (pos, vel [3]float64, err error) {
|
||
|
|
if target == center {
|
||
|
|
return pos, vel, nil
|
||
|
|
}
|
||
|
|
if stack == nil || len(stack.engines) == 0 {
|
||
|
|
return pos, vel, ErrNoEngine
|
||
|
|
}
|
||
|
|
if !finiteEpoch(jdTDB) {
|
||
|
|
return pos, vel, ErrOutOfRange
|
||
|
|
}
|
||
|
|
failures := &stackFailures{}
|
||
|
|
if pos, vel, ok := stack.direct(target, center, jdTDB, failures); ok {
|
||
|
|
return pos, vel, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
var candidateBuf [stackInlineBodies]Body
|
||
|
|
candidates := stack.candidates(candidateBuf[:0], target, center)
|
||
|
|
var queueBuf [stackInlineBodies + 1]stackNode
|
||
|
|
queue := append(queueBuf[:0], stackNode{body: target})
|
||
|
|
var visitedBuf [stackInlineBodies]bool
|
||
|
|
visited := visitedBuf[:0]
|
||
|
|
for _, candidate := range candidates {
|
||
|
|
visited = append(visited, candidate == target)
|
||
|
|
}
|
||
|
|
for head := 0; head < len(queue); head++ {
|
||
|
|
current := queue[head]
|
||
|
|
if current.body == center {
|
||
|
|
return current.pos, current.vel, nil
|
||
|
|
}
|
||
|
|
for index, candidate := range candidates {
|
||
|
|
if visited[index] || candidate == current.body {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
edgePos, edgeVel, ok := stack.direct(current.body, candidate, jdTDB, failures)
|
||
|
|
if !ok {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
addVector(&edgePos, current.pos)
|
||
|
|
addVector(&edgeVel, current.vel)
|
||
|
|
visited[index] = true
|
||
|
|
queue = append(queue, stackNode{body: candidate, pos: edgePos, vel: edgeVel})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return pos, vel, failures.err()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (stack *Stack) direct(target, center Body, jdTDB float64, failures *stackFailures) (pos, vel [3]float64, ok bool) {
|
||
|
|
for _, engine := range stack.engines {
|
||
|
|
start, end, covered := engine.Coverage(target)
|
||
|
|
if !covered || jdTDB < start || jdTDB > end {
|
||
|
|
if covered && (jdTDB < start || jdTDB > end) {
|
||
|
|
failures.add(ErrOutOfRange)
|
||
|
|
}
|
||
|
|
start, end, covered = engine.Coverage(center)
|
||
|
|
if !covered || jdTDB < start || jdTDB > end {
|
||
|
|
if covered && (jdTDB < start || jdTDB > end) {
|
||
|
|
failures.add(ErrOutOfRange)
|
||
|
|
}
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
}
|
||
|
|
pos, vel, err := engine.State(target, center, jdTDB)
|
||
|
|
if err == nil {
|
||
|
|
return pos, vel, true
|
||
|
|
}
|
||
|
|
failures.add(err)
|
||
|
|
}
|
||
|
|
return pos, vel, false
|
||
|
|
}
|
||
|
|
|
||
|
|
// candidates 按 center、固定候选、target、各源额外天体的顺序去重。
|
||
|
|
func (stack *Stack) candidates(result []Body, target, center Body) []Body {
|
||
|
|
result = appendUniqueBody(result, center)
|
||
|
|
for _, body := range chainBodies {
|
||
|
|
result = appendUniqueBody(result, body)
|
||
|
|
}
|
||
|
|
result = appendUniqueBody(result, target)
|
||
|
|
for _, body := range stack.extra {
|
||
|
|
result = appendUniqueBody(result, body)
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
func appendUniqueBody(bodies []Body, body Body) []Body {
|
||
|
|
if containsBody(bodies, body) {
|
||
|
|
return bodies
|
||
|
|
}
|
||
|
|
return append(bodies, body)
|
||
|
|
}
|
||
|
|
|
||
|
|
func containsBody(bodies []Body, body Body) bool {
|
||
|
|
for _, existing := range bodies {
|
||
|
|
if existing == body {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
type stackFailures struct {
|
||
|
|
last error
|
||
|
|
outOfRange bool
|
||
|
|
unsupported bool
|
||
|
|
}
|
||
|
|
|
||
|
|
func (failures *stackFailures) add(err error) {
|
||
|
|
if err == nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
failures.last = err
|
||
|
|
if errors.Is(err, ErrOutOfRange) {
|
||
|
|
failures.outOfRange = true
|
||
|
|
}
|
||
|
|
if errors.Is(err, ErrUnsupportedBody) {
|
||
|
|
failures.unsupported = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (failures *stackFailures) err() error {
|
||
|
|
if failures.outOfRange {
|
||
|
|
return ErrOutOfRange
|
||
|
|
}
|
||
|
|
if failures.unsupported {
|
||
|
|
return ErrUnsupportedBody
|
||
|
|
}
|
||
|
|
if failures.last != nil {
|
||
|
|
return failures.last
|
||
|
|
}
|
||
|
|
return ErrUnsupportedBody
|
||
|
|
}
|
||
|
|
|
||
|
|
func finiteEpoch(value float64) bool {
|
||
|
|
return value == value && value < 1.7976931348623157e308 && value > -1.7976931348623157e308
|
||
|
|
}
|
||
|
|
|
||
|
|
func addVector(dst *[3]float64, src [3]float64) {
|
||
|
|
for index := range dst {
|
||
|
|
dst[index] += src[index]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var _ Engine = (*Stack)(nil)
|
||
|
|
var _ BodyProvider = (*Stack)(nil)
|