-
-
Notifications
You must be signed in to change notification settings - Fork 252
Expand file tree
/
Copy pathvectorlib.py
More file actions
274 lines (212 loc) · 9.18 KB
/
vectorlib.py
File metadata and controls
274 lines (212 loc) · 9.18 KB
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
"""Vector functions and their composition."""
from jplephem.names import target_names as _jpl_code_name_dict
from numpy import max
from .constants import C_AUDAY
from .descriptorlib import reify
from .errors import DeprecationError
from .functions import length_of, _reconcile
from .positionlib import build_position
from .timelib import Time
class VectorFunction(object):
"""Given a time, computes a corresponding position."""
ephemeris = None
@reify
def vector_name(self):
return type(self).__name__
@reify
def center_name(self):
return _jpl_name(self.center)
@reify
def target_name(self):
return _jpl_name(self.target)
def __repr__(self):
return '<{0} {1}>'.format(type(self).__name__, str(self))
def __str__(self):
if self.target is self:
return self.target_name
return self.arrow_str()
def arrow_str(self):
return '{0} {1} -> {2}'.format(
self.vector_name, self.center_name, self.target_name,
)
def __add__(self, other):
if self.target != other.center:
if other.target == self.center:
self, other = other, self
else:
raise ValueError(
"you can only add two vectors"
" if the target where one of the vectors ends"
" is the center where the other vector starts"
)
self_vfs = getattr(self, 'vector_functions', None) or (self,)
other_vfs = getattr(other, 'vector_functions', None) or (other,)
return VectorSum(self.center, other.target, self_vfs + other_vfs)
def __neg__(self):
return ReversedVector(self)
def __sub__(self, other):
if self.center != other.center:
raise ValueError(
"you can only subtract two vectors"
" if they both start at the same center"
)
self_vfs = getattr(self, 'vector_functions', None) or (self,)
other_vfs = getattr(other, 'vector_functions', None) or (other,)
other_vfs = tuple(reversed([-vf for vf in other_vfs]))
return VectorSum(other.target, self.target, other_vfs + self_vfs)
def at(self, t):
"""At time ``t``, compute the target's position relative to the center.
If ``t`` is an array of times, then the returned position object
will specify as many positions as there were times. The kind of
position returned depends on the value of the ``center``
attribute:
* Solar System Barycenter: :class:`~skyfield.positionlib.Barycentric`
* Center of the Earth: :class:`~skyfield.positionlib.Geocentric`
* Anything else: :class:`~skyfield.positionlib.ICRF`
"""
if not isinstance(t, Time):
raise ValueError('please provide the at() method with a Time'
' instance as its argument, instead of the'
' value {0!r}'.format(t))
p, v, gcrs_position, message = self._at(t)
center = self.center
position = build_position(p, v, t, center, self.target)
position._ephemeris = self.ephemeris
position._observer_gcrs_au = gcrs_position
position.message = message
return position
def _observe_from_bcrs(self, observer):
if self.center != 0:
raise ValueError('you can only observe() a body whose vector'
' center is the Solar System Barycenter,'
' but this vector has the center {0}'
.format(self.center_name))
return _correct_for_light_travel_time(observer, self)
def geometry_of(self, other):
raise DeprecationError(
"""the geometry_of() method has, alas, been deprecated
This old method has been replaced by an improved interface. If you just
need your software working again, install Skyfield 0.9.1 for a quick fix:
pip install skyfield==0.9.1
Or, to update your old code, replace each operation that looks like:
position = boston.geometry_of(satellite).at(t)
with the vector math that was previously hiding inside the old method:
position = (satellite - boston).at(t)""")
def topos(self, latitude=None, longitude=None, latitude_degrees=None,
longitude_degrees=None, elevation_m=0.0, x=0.0, y=0.0):
raise DeprecationError(
"""the topos() method has, alas, been deprecated
This old method has been replaced by an improved interface. If you just
need your software working again, install Skyfield 0.9.1 for a quick fix:
pip install skyfield==0.9.1
Or, to update your old code, replace each operation that looks like:
boston = earth.topos(...)
with the vector math that was previously hiding inside the old method:
from skyfield.api import Topos
boston = earth + Topos(...)""")
def satellite(self, text):
raise DeprecationError(
"""the satellite() method has, alas, been deprecated
This old method has been replaced by an improved interface. If you just
need your software working again, install Skyfield 0.9.1 for a quick fix:
pip install skyfield==0.9.1
Or, to update your old code, replace each operation that looks like:
sat = earth.satellite(tle_text)
with the vector math (and the little bit of text manipulation) that was
previously hiding inside the old method:
from skyfield.api import EarthSatellite
line1, line2 = tle_text.splitlines()[-2:]
sat = earth + EarthSatellite(line1, line2)""")
class ReversedVector(VectorFunction):
def __init__(self, vector_function):
self.center = vector_function.target
self.target = vector_function.center
self.vector_function = vector_function
@reify
def vector_name(self):
return 'Reversed ' + self.vector_function.vector_name
@reify
def center_name(self):
return self.vector_function.target_name
@reify
def target_name(self):
return self.vector_function.center_name
def __neg__(self):
return self.vector_function
def _at(self, t):
p, v, _, message = self.vector_function._at(t)
return -p, -v, None, message
class VectorSum(VectorFunction):
def __init__(self, center, target, vector_functions):
self.center = center
self.target = target
self.vector_functions = vector_functions
# For now, just grab the first ephemeris we can find.
ephemerides = (segment.ephemeris for segment in vector_functions
if segment.ephemeris)
self.ephemeris = next(ephemerides, None)
def __str__(self):
vector_functions = self.vector_functions
lines = [' ' + segment.arrow_str() for segment in vector_functions]
return 'Sum of {0} vectors:\n{1}'.format(
len(vector_functions),
'\n'.join(lines),
)
def __repr__(self):
return '<Vector{0}>'.format(self)
def _at(self, t):
p, v = 0.0, 0.0
gcrs_position = None
vfs = self.vector_functions
for vf in vfs:
p2, v2, _, message = vf._at(t)
if vf.center == 399:
gcrs_position = -p
p, p2 = _reconcile(p, p2)
p = p + p2
v, v2 = _reconcile(v, v2)
v = v + v2
if vfs[0].center == 0 and vf.center == 399:
gcrs_position = p2
return p, v, gcrs_position, message
def _correct_for_light_travel_time(observer, target):
"""Return a light-time corrected astrometric position and velocity.
Given an `observer` that is a `Barycentric` position somewhere in
the solar system, compute where in the sky they will see the body
`target`, by computing the light-time between them and figuring out
where `target` was back when the light was leaving it that is now
reaching the eyes or instruments of the `observer`.
"""
t = observer.t
ts = t.ts
whole = t.whole
tdb_fraction = t.tdb_fraction
cposition = observer.position.au
cvelocity = observer.velocity.au_per_d
tposition, tvelocity, gcrs_position, message = target._at(t)
tposition, cposition = _reconcile(tposition, cposition)
distance = length_of(tposition - cposition)
light_time0 = 0.0
for i in range(10):
light_time = distance / C_AUDAY
delta = light_time - light_time0
if max(abs(delta), initial=0.0) < 1e-12:
break
# We assume a light travel time of at most a couple of days. A
# longer light travel time would best be split into a whole and
# fraction, for adding to the whole and fraction of TDB.
t2 = ts.tdb_jd(whole, tdb_fraction - light_time)
tposition, tvelocity, gcrs_position, message = target._at(t2)
distance = length_of(tposition - cposition)
light_time0 = light_time
else:
raise ValueError('light-travel time failed to converge')
tvelocity, cvelocity = _reconcile(tvelocity, cvelocity)
return tposition - cposition, tvelocity - cvelocity, t, light_time
def _jpl_name(target):
if not isinstance(target, int):
return type(target).__name__
name = _jpl_code_name_dict.get(target)
if name is None:
return str(target)
return '{0} {1}'.format(target, name)