-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathEphemeris.swift
184 lines (166 loc) · 6.22 KB
/
Ephemeris.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//
// Ephemeris.swift
// StarCatalog
//
// Created by Ben Lu on 11/13/16.
// Copyright © 2016 Ben Lu. All rights reserved.
//
import Foundation
import SpaceTime
import MathUtil
/// Ephemeris is a tree structure with celestial bodies ordered in a way that satellites are always children of their respective primaries.
public class Ephemeris: NSObject, Sequence, NSCopying {
typealias Node = CelestialBody
public let root: CelestialBody
public var timestamp: JulianDay? {
if let ref = self.first(where: { $0.motion?.julianDay != nil }) {
return ref.motion!.julianDay!
}
return nil
}
public var referenceTimestamp: JulianDay? {
if let ref = self.first(where: { $0.motion?.julianDay != nil }), let mm = ref.motion as? OrbitalMotionMoment {
return mm.ephemerisJulianDay
}
return nil
}
init(solarSystemBodies: Set<CelestialBody>) {
let sortedBodies = solarSystemBodies.sorted()
guard let first = sortedBodies.first else {
fatalError("cannot initalize ephemeris from empty celestial bodies")
}
root = first
var parents = [CelestialBody]()
parents.append(first)
let remaining = sortedBodies.dropFirst()
remaining.forEach { (current) in
repeat {
if parents.isEmpty {
fatalError("solar system bodies missing")
}
let parent = parents.last!
if current.naif.isSatellite(of: parent.naif) {
parent.addSatellite(satellite: current)
current.centerBody = parent
parents.append(current)
break
} else {
parents.removeLast()
}
} while true
}
}
private init(root: CelestialBody) {
self.root = root
}
public func makeIterator() -> AnyIterator<CelestialBody> {
var result = [CelestialBody]()
var queue = [root]
while queue.isEmpty == false {
let current = queue.removeFirst()
result.append(current)
let sats = Array(current.satellites) as! [CelestialBody]
queue.append(contentsOf: sats)
}
return AnyIterator {
guard let first = result.first else {
return nil
}
result.removeFirst()
return first
}
}
public func updateMotion(using julianDay: JulianDay = JulianDay.now) {
for body in self {
if let moment = body.motion as? OrbitalMotionMoment {
moment.julianDay = julianDay
}
}
}
public subscript(naif: Naif) -> CelestialBody? {
return self.first { $0.naif == naif }
}
public subscript(naifId: Int) -> CelestialBody? {
return self.first { $0.naifId == naifId }
}
// MARK: - NSCopying
public func copy(with zone: NSZone? = nil) -> Any {
return Ephemeris(root: root.copy() as! CelestialBody)
}
}
public extension Ephemeris {
public func debugPrintReferenceJulianDayInfo() {
print("--- ephemeris info ---")
for body in self {
if body is Sun { continue }
guard let motion = body.motion else { continue }
print("\(body.name): \(motion)")
}
}
}
public extension Ephemeris {
func observedPosition(of origin: CelestialBody, fromObserver observer: CelestialBody) -> Vector3 {
return observerdPosition(Vector3.zero, relativeTo: origin, fromObserver: observer)
}
func observerdPosition(_ position: Vector3, relativeTo reference: CelestialBody? = nil, fromObserver observer: CelestialBody) -> Vector3 {
let referencePosition: Vector3
if let reference = reference {
referencePosition = reference.positionRelativeToSun!
} else {
referencePosition = Vector3.zero
}
// TODO: obblique only works on earth
let observedPosition = (referencePosition + position - observer.positionRelativeToSun!).oblique(by: observer.obliquity)
return observedPosition
}
/// Find the body closest to a direction viewed from an observer.
///
/// - Parameters:
/// - unitPosition: Unit vector.
/// - observer: The observer.
/// - maximumAngularDistance: Will ignore bodies with greater angular separation than this value.
/// - Returns: The closest body to a direction viewed from an observer
func closestBody(toUnitPosition unitPosition: Vector3, from observer: CelestialBody, maximumAngularDistance: RadianAngle = RadianAngle(Double.pi * 2)) -> CelestialBody? {
return self.map { [weak self] (targetBody) -> (body: CelestialBody, separation: Double) in
let vec = self!.observedPosition(of: targetBody, fromObserver: observer).normalized()
return (targetBody, unitPosition.angularSeparation(from: vec))
}.filter { (targetBody, separation) -> Bool in
return observer != targetBody && separation <= maximumAngularDistance.wrappedValue
}.sorted(by: { $0.1 < $1.1 }).first?.0
}
}
extension CelestialBody {
var positionRelativeToSun: Vector3? {
guard var pos = position else {
return nil
}
var current = self
while current.centerBody != nil {
current = current.centerBody!
pos += current.position!
}
return pos
}
func commonCentralBody(with other: CelestialBody) -> CelestialBody {
var list1 = [CelestialBody]()
var list2 = [CelestialBody]()
list1.append(self)
list2.append(other)
var currentBody = self
while currentBody.centerBody != nil {
list1.append(currentBody.centerBody!)
currentBody = currentBody.centerBody!
}
currentBody = other
while currentBody.centerBody != nil {
list2.append(currentBody.centerBody!)
currentBody = currentBody.centerBody!
}
for body in list1 {
if let common = list2.first(where: { $0.naif == body.naif }) {
return common
}
}
fatalError("Two celestial bodies do not share a common center")
}
}