4

I have been writing a Python script (GitHub LINK) for visualizing asteroid/comet/meteoroid orbits. The script also plots the position of planets and their orbits.

It works just right for orbits with small semi-major axis (i.e. "smaller" orbits). But when I have an orbit that goes way beyond Neptune (e.g. of a Halley-type comet), and from certain perspectives, there is a weird "wraparound" (for lack of a better word) effect.

Let me show you what I mean:

Image compilation: https://i.stack.imgur.com/1WgX0.png enter image description here

  1. This image shows the plot from a perspective where it does not break.

  2. When you rotate the same plot a bit to the right, it is as if the orbit folded in half and reversed its direction!

  3. And if you look at the plot from a great distance, you can see that the elipse is plotted as it should be.

And here is a minimal version of the code with which the issue can be reproduced. The "wraparound" occurs only when the perspective of the camera is closely parallel with the large orbit.

from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt

def orbitalElements2Cartesian(a, e, I, peri, node, E):
    """ Convert orbital elements to Cartesian coordinates in the Solar System.

    Args: 
        a (float): semi-major axis (AU)
        e (float): eccentricity
        I (float): inclination (degrees)
        peri (float): longitude of perihelion (degrees)
        node (float): longitude of ascending node (degrees)
        E (float): eccentric anomaly (radians)

    """

    # The source of equations used:
    # http://farside.ph.utexas.edu/teaching/celestial/Celestialhtml/node34.html

    # Check if the orbit is parabolic or hyperbolic
    if e >=1:
        e = 0.99999999

    # Convert degrees to radians
    I, peri, node = map(np.radians, [I, peri, node])

    # True anomaly
    theta = 2*np.arctan(np.sqrt((1.0 + e)/(1.0 - e))*np.tan(E/2.0))

    # Distance from the Sun to the poin on orbit
    r = a*(1.0 - e*np.cos(E))

    # Cartesian coordinates
    x = r*(np.cos(node)*np.cos(peri + theta) - np.sin(node)*np.sin(peri + theta)*np.cos(I))
    y = r*(np.sin(node)*np.cos(peri + theta) + np.cos(node)*np.sin(peri + theta)*np.cos(I))
    z = r*np.sin(peri + theta)*np.sin(I)

    return x, y, z


if __name__ == '__main__':

    # Example orbital elements
    # a, e, incl, peri, node
    orb_elements = np.array([
        [2.363, 0.515, 4.0, 205.0, 346.1],
        [0.989, 0.089, 3.1, 55.6, 21.2],
        [0.898, 0.460, 1.3, 77.1, 331.2],
        [104.585332285, 0.994914, 89.3950, 130.8767, 282.4633]
        ])

    # Setup the plot
    fig = plt.figure()
    ax = fig.gca(projection='3d')


    # Eccentric anomaly (full range)
    E = np.linspace(-np.pi, np.pi, 100)

    # Plot the given orbits
    for i, orbit in enumerate(orb_elements):
        a, e, I, peri, node = orbit

        # Take extra steps in E if the orbit is very large
        if a > 50:
            E = np.linspace(-np.pi, np.pi, (a/20.0)*100)

        # Get the orbit in the cartesian space
        x, y, z = orbitalElements2Cartesian(a, e, I, peri, node, E)

        # Plot orbits
        ax.plot(x, y, z, c='#32CD32')

    # Add limits (in AU)
    ax.set_xlim3d(-5,5)
    ax.set_ylim3d(-5,5)
    ax.set_zlim3d(-5,5)

    plt.tight_layout()
    plt.show()

I am a bit dumbfounded by this and cannot seem to find a proper solution. I would greatly appreciate some help!

D Vida
  • 51
  • 3
  • Please provide a minimal example (inline) to reproduce this. – tacaswell Apr 14 '16 at 03:48
  • @tcaswell Thank you for the suggestion! I have edited the post. – D Vida Apr 14 '16 at 06:43
  • looks like wrongly culled curve before rendering. Those 2 big lines cross the whole screen are most likely the 2 segments of curve intersecting the projection plane. One point is visible or very near visible area and the second is not. as the segment does not been culled off it was rendered and interpolated through the screen even if it is most likely behind camera (that is why is mirrored). at least that is my bet. Try to render only segments that are fully visible. – Spektre Apr 14 '16 at 06:45
  • @Spektre It seems like I should post a bug report to matplotlib developers about this, this does not seem to be a minor bug then. – D Vida Apr 14 '16 at 10:02
  • @DVida most likely it is just wrong culling index +/-1 somewhere in the culling test or they do not cull at all (and the lines are just out of bound access from different scanline) and single `if` would repair it. So most likely it is minor bug. I render stuff like this by my self either by GDI or OpenGL without any problems but at least I know what I am doing. Most people today use lib/framework for any trivial thing loosing the knowledge behind it. – Spektre Apr 14 '16 at 10:30

2 Answers2

0

matplotlib isn't great for complex 3D plots in my experience (I've had similar strange behaviour with out of axis values). Something like mayavi could be worth considering as it's designed for 3D plots...

A possible workaround is given in this blog, basically just set out of axis values to np.NaN for your required axis. If I add the following to your example,

for r in [x,y,z]:
    for i in np.arange(len(r)):
        if r[i] < -5:
            x[i] = np.NaN
            y[i] = np.NaN
            z[i] = np.NaN
        elif r[i] > 5:
            x[i] = np.NaN
            y[i] = np.NaN
            z[i] = np.NaN
        else:
            pass

it removes the wraparound.

Ed Smith
  • 12,716
  • 2
  • 43
  • 55
  • Mayavi looks promising, thanks! Now the problem with clipping everything beyond some arbitrary limit is that when you look at the orbit from a ceratin perspective you will see that it is clipped, which is not really acceptable for orbital visualisations. That would imply that the orbit is possibly parabolic or hyperbolic, not elliptic. Nevertheless, thank you for the answer! – D Vida Apr 14 '16 at 10:32
0

I had similar issues and wanted to make something a bit more user friendly. I moved all of the functions in this library over to javascript and created a webGL interface in Three.js which lets you do what you want here but also plots the location of the asteroid / comet with animation via time functions. Just need a web browser to use it. Check it out :)

http://rankinstudio.com/asteroids/asteroids.html

Rankinstudio
  • 591
  • 6
  • 19