Catch that asteroid!¶
In [1]:
import matplotlib.pyplot as plt
plt.ion()
from astropy import units as u
from astropy.time import Time
In [2]:
from astropy.utils.data import conf
conf.dataurl
Out[2]:
'http://data.astropy.org/'
In [3]:
conf.remote_timeout
Out[3]:
10.0
First, we need to increase the timeout time to allow the download of data occur properly
In [4]:
conf.remote_timeout = 10000
Then, we do the rest of the imports and create our initial orbits.
In [5]:
from astropy.coordinates import solar_system_ephemeris
solar_system_ephemeris.set("jpl")
from poliastro.bodies import *
from poliastro.twobody import Orbit
from poliastro.plotting import OrbitPlotter, plot
EPOCH = Time("2017-09-01 12:05:50", scale="tdb")
In [6]:
earth = Orbit.from_body_ephem(Earth, EPOCH)
earth
Out[6]:
1 x 1 AU x 23.4 deg orbit around Sun (☉)
In [7]:
plot(earth, label=Earth);

In [8]:
from poliastro.neos import neows
In [9]:
florence = neows.orbit_from_name("Florence")
florence
Out[9]:
1 x 3 AU x 22.1 deg orbit around Sun (☉)
Two problems: the epoch is not the one we desire, and the inclination is with respect to the ecliptic!
In [10]:
florence.epoch
Out[10]:
<Time object: scale='tdb' format='jd' value=2458200.5>
In [11]:
florence.epoch.iso
Out[11]:
'2018-03-23 00:00:00.000'
In [12]:
florence.inc
Out[12]:
We first propagate:
In [13]:
florence = florence.propagate(EPOCH)
florence.epoch.tdb.iso
Out[13]:
'2017-09-01 12:05:50.000'
And now we have to convert to another reference frame, using http://docs.astropy.org/en/stable/coordinates/.
In [14]:
from astropy.coordinates import (
ICRS, GCRS,
CartesianRepresentation, CartesianDifferential
)
from poliastro.frames import HeliocentricEclipticJ2000
The NASA servers give the orbital elements of the asteroids in an Heliocentric Ecliptic frame. Fortunately, it is already defined in Astropy:
In [15]:
florence_heclip = HeliocentricEclipticJ2000(
x=florence.r[0], y=florence.r[1], z=florence.r[2],
v_x=florence.v[0], v_y=florence.v[1], v_z=florence.v[2],
representation=CartesianRepresentation,
differential_type=CartesianDifferential,
obstime=EPOCH
)
florence_heclip
Out[15]:
<HeliocentricEclipticJ2000 Coordinate (obstime=2017-09-01 12:05): (x, y, z) in km
(1.45898575e+08, -58567565.51292471, 2279107.77284053)
(v_x, v_y, v_z) in km / s
(7.40829055, 31.11151455, 12.79669447)>
Now we just have to convert to ICRS, which is the “standard” reference in which poliastro works:
In [16]:
florence_icrs_trans = florence_heclip.transform_to(ICRS)
florence_icrs_trans.representation = CartesianRepresentation
florence_icrs_trans
Out[16]:
<ICRS Coordinate: (x, y, z) in km
(1.46265478e+08, -53881737.42618975, -20898600.42756944)
(v_x, v_y, v_z) in km / s
(7.39988214, 23.46299465, 24.12028277)>
In [17]:
florence_icrs = Orbit.from_vectors(
Sun,
r=[florence_icrs_trans.x, florence_icrs_trans.y, florence_icrs_trans.z] * u.km,
v=[florence_icrs_trans.v_x, florence_icrs_trans.v_y, florence_icrs_trans.v_z] * (u.km / u.s),
epoch=florence.epoch
)
florence_icrs
Out[17]:
1 x 3 AU x 44.5 deg orbit around Sun (☉)
In [18]:
florence_icrs.rv()
Out[18]:
(<Quantity [ 1.46265478e+08, -5.38817374e+07, -2.08986004e+07] km>,
<Quantity [ 7.39988214, 23.46299465, 24.12028277] km / s>)
Let us compute the distance between Florence and the Earth:
In [19]:
from poliastro.util import norm
In [20]:
norm(florence_icrs.r - earth.r) - Earth.R
Out[20]:
This value is consistent with what ESA says! \(7\,060\,160\) km
In [21]:
from IPython.display import HTML
HTML(
"""<blockquote class="twitter-tweet" data-lang="en"><p lang="es" dir="ltr">La <a href="https://twitter.com/esa_es">@esa_es</a> ha preparado un resumen del asteroide <a href="https://twitter.com/hashtag/Florence?src=hash">#Florence</a> 😍 <a href="https://t.co/Sk1lb7Kz0j">pic.twitter.com/Sk1lb7Kz0j</a></p>— AeroPython (@AeroPython) <a href="https://twitter.com/AeroPython/status/903197147914543105">August 31, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>"""
)
Out[21]:
La @esa_es ha preparado un resumen del asteroide #Florence 😍 pic.twitter.com/Sk1lb7Kz0j
— AeroPython (@AeroPython) August 31, 2017
And now we can plot!
In [22]:
frame = OrbitPlotter()
frame.plot(earth, label="Earth")
frame.plot(Orbit.from_body_ephem(Mars, EPOCH))
frame.plot(Orbit.from_body_ephem(Venus, EPOCH))
frame.plot(Orbit.from_body_ephem(Mercury, EPOCH))
frame.plot(florence_icrs, label="Florence");

The difference between doing it well and doing it wrong is clearly visible:
In [23]:
frame = OrbitPlotter()
frame.plot(earth, label="Earth")
frame.plot(florence, label="Florence (Ecliptic)")
frame.plot(florence_icrs, label="Florence (ICRS)");

And now let’s do something more complicated: express our orbit with respect to the Earth! For that, we will use GCRS, with care of setting the correct observation time:
In [24]:
florence_gcrs_trans = florence_heclip.transform_to(GCRS(obstime=EPOCH))
florence_gcrs_trans.representation = CartesianRepresentation
florence_gcrs_trans
Out[24]:
<GCRS Coordinate (obstime=2017-09-01 12:05, obsgeoloc=(0., 0., 0.) m, obsgeovel=(0., 0., 0.) m / s): (x, y, z) in km
(4960528.39215574, -5020204.25120256, 306195.44251179)
(v_x, v_y, v_z) in km / s
(-2.76863626, -1.95773245, 13.09966915)>
In [25]:
florence_hyper = Orbit.from_vectors(
Earth,
r=[florence_gcrs_trans.x, florence_gcrs_trans.y, florence_gcrs_trans.z] * u.km,
v=[florence_gcrs_trans.v_x, florence_gcrs_trans.v_y, florence_gcrs_trans.v_z] * (u.km / u.s),
epoch=EPOCH
)
florence_hyper
Out[25]:
7064205 x -7068561 km x 104.3 deg orbit around Earth (♁)
Notice that the ephemerides of the Moon is also given in ICRS, and therefore yields a weird hyperbolic orbit!
In [26]:
moon = Orbit.from_body_ephem(Moon, EPOCH)
moon
Out[26]:
151218466 x -151219347 km x 23.3 deg orbit around Earth (♁)
In [27]:
moon.a
Out[27]:
In [28]:
moon.ecc
Out[28]:
So we have to convert again.
In [29]:
moon_icrs = ICRS(
x=moon.r[0], y=moon.r[1], z=moon.r[2],
v_x=moon.v[0], v_y=moon.v[1], v_z=moon.v[2],
representation=CartesianRepresentation,
differential_type=CartesianDifferential
)
moon_icrs
Out[29]:
<ICRS Coordinate: (x, y, z) in km
(1.41399531e+08, -49228391.42507221, -21337616.62766309)
(v_x, v_y, v_z) in km / s
(11.10890252, 25.6785744, 11.0567569)>
In [30]:
moon_gcrs = moon_icrs.transform_to(GCRS(obstime=EPOCH))
moon_gcrs.representation = CartesianRepresentation
moon_gcrs
Out[30]:
<GCRS Coordinate (obstime=2017-09-01 12:05, obsgeoloc=(0., 0., 0.) m, obsgeovel=(0., 0., 0.) m / s): (x, y, z) in km
(94189.90120828, -367278.24304992, -133087.21297573)
(v_x, v_y, v_z) in km / s
(0.94073662, 0.25786326, 0.03569047)>
In [31]:
moon = Orbit.from_vectors(
Earth,
[moon_gcrs.x, moon_gcrs.y, moon_gcrs.z] * u.km,
[moon_gcrs.v_x, moon_gcrs.v_y, moon_gcrs.v_z] * (u.km / u.s),
epoch=EPOCH
)
moon
Out[31]:
367937 x 405209 km x 19.4 deg orbit around Earth (♁)
And finally, we plot the Moon:
In [32]:
plot(moon, label=Moon)
plt.gcf().autofmt_xdate()

And now for the final plot:
In [33]:
frame = OrbitPlotter()
# This first plot sets the frame
frame.plot(florence_hyper, label="Florence")
# And then we add the Moon
frame.plot(moon, label=Moon)
plt.xlim(-1000000, 8000000)
plt.ylim(-5000000, 5000000)
plt.gcf().autofmt_xdate()
/home/juanlu/.miniconda36/envs/poliastro36/lib/python3.6/site-packages/astropy/units/quantity.py:639: RuntimeWarning:
overflow encountered in cosh
/home/juanlu/.miniconda36/envs/poliastro36/lib/python3.6/site-packages/astropy/units/quantity.py:639: RuntimeWarning:
overflow encountered in sinh
/home/juanlu/.miniconda36/envs/poliastro36/lib/python3.6/site-packages/astropy/units/quantity.py:639: RuntimeWarning:
invalid value encountered in true_divide
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-33-ccd86024eefa> in <module>()
2
3 # This first plot sets the frame
----> 4 frame.plot(florence_hyper, label="Florence")
5
6 # And then we add the Moon
~/Development/poliastro/poliastro-library/src/poliastro/plotting.py in plot(self, orbit, label, color)
169 self.set_attractor(orbit.attractor)
170 self._redraw_attractor(orbit.r_p * 0.15) # Arbitrary Threshhold
--> 171 _, positions = orbit.sample(self.num_points)
172
173 x0, y0 = self._project(orbit.r[None])
~/Development/poliastro/poliastro-library/src/poliastro/twobody/orbit.py in sample(self, values, method)
331 nu_values = np.linspace(-nu_limit, nu_limit, values)
332
--> 333 return self.sample(nu_values, method)
334
335 elif hasattr(values, "unit") and values.unit in ('rad', 'deg'):
~/Development/poliastro/poliastro-library/src/poliastro/twobody/orbit.py in sample(self, values, method)
336 values = self._generate_time_values(values)
337
--> 338 return values, self._sample(values, method)
339
340 def _sample(self, time_values, method=mean_motion):
~/Development/poliastro/poliastro-library/src/poliastro/twobody/orbit.py in _sample(self, time_values, method)
341 values = np.zeros((len(time_values), 3)) * self.r.unit
342 for ii, epoch in enumerate(time_values):
--> 343 rr = self.propagate(epoch, method).r
344 values[ii] = rr
345
~/Development/poliastro/poliastro-library/src/poliastro/twobody/orbit.py in propagate(self, value, method, rtol, **kwargs)
273 time_of_flight = time.TimeDelta(value)
274
--> 275 return propagate(self, time_of_flight, method=method, rtol=rtol, **kwargs)
276
277 def sample(self, values=None, method=mean_motion):
~/Development/poliastro/poliastro-library/src/poliastro/twobody/propagation.py in propagate(orbit, time_of_flight, method, rtol, **kwargs)
207
208 """
--> 209 r, v = method(orbit, time_of_flight.to(u.s).value, rtol=rtol, **kwargs)
210 return orbit.from_vectors(orbit.attractor, r * u.km, v * u.km / u.s, orbit.epoch + time_of_flight)
211
~/Development/poliastro/poliastro-library/src/poliastro/twobody/propagation.py in mean_motion(orbit, tof, **kwargs)
140 with u.set_enabled_equivalencies(u.dimensionless_angles()):
141 M = M0 + tof * np.sqrt(k / np.abs(a ** 3)) * u.rad
--> 142 nu = M_to_nu(M, ecc)
143
144 # parabolic orbit
~/Development/poliastro/poliastro-library/src/poliastro/twobody/angles.py in M_to_nu(M, ecc)
231 """
232 if ecc > 1:
--> 233 F = M_to_F(M, ecc)
234 nu = F_to_nu(F, ecc)
235 else:
~/Development/poliastro/poliastro-library/src/poliastro/twobody/angles.py in M_to_F(M, ecc)
158 with u.set_enabled_equivalencies(u.dimensionless_angles()):
159 F = optimize.newton(_kepler_equation_hyper, M, _kepler_equation_prime_hyper,
--> 160 args=(M, ecc))
161 return F
162
~/.miniconda36/envs/poliastro36/lib/python3.6/site-packages/scipy/optimize/zeros.py in newton(func, x0, fprime, args, tol, maxiter, fprime2)
213 q1 = func(*((p1,) + args))
214 msg = "Failed to converge after %d iterations, value is %s" % (maxiter, p)
--> 215 raise RuntimeError(msg)
216
217
RuntimeError: Failed to converge after 50 iterations, value is nan rad

Per Python ad astra!