Repository: poliastro/poliastro

Issue:
Cannot propagate orbits around custom bodies
Even after fixing the case of sampling #649, orbits around custom bodies still can't be propagated:

```
>>> from poliastro.twobody import Orbit
>>> from poliastro.bodies import Moon
>>> from astropy import units as u
>>> orb = Orbit.from_vectors(Moon, [-17621.48704193,   9218.72943252, -10947.19144579] * u.km, [ -6985.91793854, -13970.02807282,  -7272.34554685] * u.km / u.day)
>>> orb
2211 x 22792 km x 36.0 deg orbit around Moon (☾) at epoch J2000.000 (TT)
>>> orb.sample(3)
/home/juanlu/Development/poliastro/poliastro-library/src/poliastro/twobody/orbit.py:1082: UserWarning:

No frame found for attractor Moon (☾), returning only cartesian coordinates instead

<CartesianRepresentation (x, y, z) in km
    [(  1738.77087758, -814.03020271,   1096.10391796),
     (-17926.29064628, 8392.44675468, -11300.55584963),
     (  1738.77087758, -814.03020271,   1096.10391796)]
 (has differentials w.r.t.: 's')>
>>> orb.propagate(orb.period / 2)
Traceback (most recent call last):
  File "/home/juanlu/Development/poliastro/poliastro-library/src/poliastro/frames.py", line 337, in get_frame
    frames = _FRAME_MAPPING[attractor]
KeyError: Moon (☾)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/juanlu/Development/poliastro/poliastro-library/src/poliastro/twobody/orbit.py", line 913, in propagate
    if "obstime" in self.frame.frame_attributes:
  File "/home/juanlu/Development/poliastro/poliastro-library/src/poliastro/twobody/orbit.py", line 101, in frame
    self._frame = get_frame(self.attractor, self._plane, self.epoch)
  File "/home/juanlu/Development/poliastro/poliastro-library/src/poliastro/frames.py", line 340, in get_frame
    "Frames for orbits around custom bodies are not yet supported"
NotImplementedError: Frames for orbits around custom bodies are not yet supported
```

To fix this, we should:

1. Add a test, similar to #650, but using `.propagate` instead of `.sample`
2. Try a similar logic to propagate orbits without frames

Discussion:

Patch:
diff --git a/appveyor.yml b/appveyor.yml
index 89251ed7..59642987 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -20,9 +20,6 @@ branches:
 
 environment:
   matrix:
-    - TOXENV: check
-      PYTHON: C:\Python36\python.exe
-
     - TOXENV: 'py36'
       PYTHON: C:\Python36-x64\python.exe
 
diff --git a/src/poliastro/twobody/orbit.py b/src/poliastro/twobody/orbit.py
index f180587c..9774f990 100644
--- a/src/poliastro/twobody/orbit.py
+++ b/src/poliastro/twobody/orbit.py
@@ -924,27 +924,32 @@ class Orbit(object):
             time_of_flight = time.TimeDelta(value)
 
         cartesian = propagate(self, time_of_flight, method=method, rtol=rtol, **kwargs)
+        new_epoch = self.epoch + time_of_flight
 
+        # TODO: Unify with sample
         # If the frame supports obstime, set the time values
-        kwargs = {}
-        if "obstime" in self.frame.frame_attributes:
-            kwargs["obstime"] = self.epoch + time_of_flight
-        else:
-            warn(
-                "Frame {} does not support 'obstime', time values were not returned".format(
-                    self.frame.__class__
-                )
+        try:
+            kwargs = {}
+            if "obstime" in self.frame.frame_attributes:
+                kwargs["obstime"] = new_epoch
+
+            # Use of a protected method instead of frame.realize_frame
+            # because the latter does not let the user choose the representation type
+            # in one line despite its parameter names, see
+            # https://github.com/astropy/astropy/issues/7784
+            coords = self.frame._replicate(
+                cartesian, representation_type="cartesian", **kwargs
             )
 
-        # Use of a protected method instead of frame.realize_frame
-        # because the latter does not let the user choose the representation type
-        # in one line despite its parameter names, see
-        # https://github.com/astropy/astropy/issues/7784
-        coords = self.frame._replicate(
-            cartesian, representation_type="cartesian", **kwargs
-        )
+            return self.from_coords(self.attractor, coords, plane=self.plane)
 
-        return self.from_coords(self.attractor, coords, plane=self.plane)
+        except NotImplementedError:
+            return self.from_vectors(
+                self.attractor,
+                cartesian.xyz,
+                cartesian.differentials["s"].d_xyz,
+                new_epoch,
+            )
 
     @u.quantity_input(value=u.rad)
     def propagate_to_anomaly(self, value):
diff --git a/tox.ini b/tox.ini
index 20b2af3e..69731452 100644
--- a/tox.ini
+++ b/tox.ini
@@ -17,7 +17,7 @@ basepython =
     py35: {env:PYTHON:python3.5}
     {py36,docs}: {env:PYTHON:python3.6}
     py37: {env:PYTHON:python3.7}
-    {clean,check}: {env:PYTHON:python3}
+    {clean,check,reformat}: {env:PYTHON:python3}
 setenv =
     PYTHONPATH={toxinidir}/tests
     PYTHONUNBUFFERED=yes


Test patch:
diff --git a/src/poliastro/tests/tests_twobody/test_propagation.py b/src/poliastro/tests/tests_twobody/test_propagation.py
index f9c07e7f..9ad036aa 100644
--- a/src/poliastro/tests/tests_twobody/test_propagation.py
+++ b/src/poliastro/tests/tests_twobody/test_propagation.py
@@ -329,7 +329,7 @@ def test_propagation_sets_proper_epoch():
     assert propagated.epoch == expected_epoch
 
 
-def test_propagation_custom_body_raises_warning_and_returns_coords():
+def test_sample_custom_body_raises_warning_and_returns_coords():
     # See https://github.com/poliastro/poliastro/issues/649
     orbit = Orbit.circular(Moon, 100 * u.km)
 
@@ -340,3 +340,9 @@ def test_propagation_custom_body_raises_warning_and_returns_coords():
 
     assert isinstance(coords, CartesianRepresentation)
     assert len(coords) == 10
+
+
+def test_propagation_custom_body_works():
+    # See https://github.com/poliastro/poliastro/issues/649
+    orbit = Orbit.circular(Moon, 100 * u.km)
+    orbit.propagate(1 * u.h)