05. Near-Earth Objects#
Orbital Properties of Near-Earth Objects (NEOs)
Contact author(s): Sarah Greenstreet
Last verified to run: 2024-04-24
LSST Science Pipelines version: Weekly 2024_04
Container Size: medium
Targeted learning level: beginner
Description: An introduction to near-Earth objects (NEOs) in the DP0.3 dataset, including illustrations of the four NEOs sub-populations: Amors, Apollos, Atens, and Atiras.
Skills: Use TAP queries to retrieve Solar System objects. Plot properties and orbits of near-Earth objects.
LSST Data Products: TAP DP0.3 MPCORB (10-year) and SSObject tables.
Packages: lsst.rsp.get_tap_service
Credit: Originally developed by Sarah Greenstreet and the Rubin Community Science Team in the context of the Rubin DP0.
Get Support: Find DP0-related documentation and resources at dp0.lsst.io. Questions are welcome as new topics in the Support - Data Preview 0 Category of the Rubin Community Forum. Rubin staff will respond to all questions posted there.
1. Introduction¶
This tutorial demonstrates how to use TAP to query the MPCORB and SSObject tables. The tables are queried simultaneously and joined on their SSObjectId. The properties of near-Earth objects (NEOs; perihelia $q$<1.3 au) from the DP0.3 catalogs are examined. The NEOs in this sample are restricted to those with semimajor axes $a$<4.0 au, and eccentricities $e$<1.0. The orbital properties of NEOs are examined, including plotting the planet-crossing regions and boundaries that define the four NEO sub-populations: Amors, Apollos, Atens, and Atiras.
1.1 NEO Dynamical Classes¶
Near-Earth objects (NEOs) are traditionally divided into four dynamical sub-populations based on their orbital parameters compared to the orbit of the Earth. The Earth's orbit is nearly circular with a semimajor axis $a$ = 1.0 au and eccentricity $e$ = 0.017. This gives the Earth a perihelion distance $q$ = 0.983 au and an aphelion distance of $Q$ = 1.017 au. The four NEO sub-populations (Amors, Apollos, Atens, and Atiras) are divided using these orbital parameters of the Earth as follows:
Amors have orbits exterior to Earth's orbit with perihelia larger than Earth's aphelion and less than 1.3 au (1.017 au < $q$ < 1.3 au). Amors are always farther from the Sun than Earth.
Apollos are on Earth-crossing orbits with perihelia smaller than Earth's aphelion ($q$ < 1.017 au) and semimajor axes $a$ > 1.0 au. Apollos are Earth-crossers with semimajor axes larger than Earth's orbit.
Atens are on Earth-crossing orbits with aphelia larger than Earth's perihelion ($Q$ > 0.983 au) and semimajor axes $a$ < 1.0 au. Atens are Earth-crossers with semimajor axes smaller than Earth's orbit.
Atiras have orbits interior to Earth's orbit with aphelia smaller than Earth's perihelion ($Q$ < 0.983 au). Atiras are always closer to the Sun than Earth.
The schematic diagram shown below illustrates examples of Amor, Apollos, Aten, and Atira orbits compared to Earth's orbit.
This notebook provides examples of plotting the boundaries for each NEO sub-population in semimajor axis $a$ versus eccentricity $e$ and how to divide the NEOs queried from the DP0.3 tables into these four sub-populations.
1.2 Package Imports¶
The matplotlib
and numpy
libraries are widely used Python libraries for plotting and scientific computing. We will use these packages below, including the matplotlib.pyplot plotting sublibrary.
We also use the lsst.rsp
package to access the TAP service and query the DP0 catalogs.
# general python packages
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
# LSST package for TAP queries
from lsst.rsp import get_tap_service
1.3 Define Functions and Parameters¶
1.3.1 Define functions for computing orbital parameters not included in the MPCORB table¶
Define a function to convert a given perihelion distance ($q$) and eccentricity ($e$) to an orbital semimajor axis ($a$). Their relationship is defined by $q=a*(1−e)$.
def calc_semimajor_axis(q, e):
"""
Given a perihelion distance and orbital eccentricity,
calculate the semi-major axis of the orbit.
Parameters
----------
q: ndarray
Distance at perihelion, in au.
e: ndarray
Orbital eccentricity.
Returns
-------
a: ndarray
Semi-major axis of the orbit, in au.
q = a(1-e), so a = q/(1-e)
"""
return q / (1.0 - e)
Define a function to convert a given perihelion distance ($q$) and eccentricity ($e$) to an aphelion distance ($Q$). Their relationships are defined by: $Q=q*(1+e)/(1-e)$.
def calc_aphelion(q, e):
"""
Given a perihelion distance and orbital eccentricity,
calculate the semi-major axis of the orbit.
Parameters
----------
q: ndarray
Distance at perihelion, in au.
e: ndarray
Orbital eccentricity.
Returns
-------
Q: ndarray
Distance at aphelion, in au.
Q = q*(1+e)/(1-e)
"""
return q * (1.0 + e) / (1.0 - e)
1.3.2 Define functions to return arrays needed for plotting planet-crossing curves on an ($a$, $e$) plot¶
Define a function to return an array with ($a$, $e$) values needed to plot a curve showing the aphelion at a planet's perihelion.
def get_Q_at_planets_q_curve_values(q_planet):
a_Q = np.arange(0.001, q_planet, 0.001)
e_Q = (q_planet / a_Q) - 1.0
return (a_Q, e_Q)
Define a function to return an array with ($a$, $e$) values needed to plot a curve showing the perihelion at a planet's aphelion.
def get_q_at_planets_Q_curve_values(Q_planet):
a_q = np.arange(Q_planet, 4.0, 0.001)
e_q = (1.0 - (Q_planet / a_q))
return (a_q, e_q)
Define the perihelia and aphelia for the terrestrial planets and the perihelion boundary for NEOs.
q_Earth = 0.983
Q_Earth = 1.017
q_Mercury = 0.307
Q_Mercury = 0.467
q_Venus = 0.718
Q_Venus = 0.728
q_Mars = 1.381
Q_Mars = 1.666
q_NEOs = 1.3
Set some plotting defaults to make plots look nice.
plt.style.use('tableau-colorblind10')
%matplotlib inline
params = {'axes.labelsize': 15,
'font.size': 15,
'legend.fontsize': 10,
'xtick.major.width': 2,
'xtick.minor.width': 1,
'xtick.major.size': 6,
'xtick.minor.size': 3,
'xtick.direction': 'in',
'xtick.top': True,
'lines.linewidth': 2,
'axes.linewidth': 2,
'axes.labelweight': 1,
'axes.titleweight': 1,
'ytick.major.width': 1,
'ytick.minor.width': 2,
'ytick.major.size': 6,
'ytick.minor.size': 3,
'ytick.direction': 'in',
'ytick.right': True,
'figure.figsize': [6, 6],
'figure.facecolor': 'White'
}
plt.rcParams.update(params)
2. Orbital Parameters of Near-Earth Objects (NEOs)¶
2.1 Query the DP0.3 catalogs to create a table of NEOs and their orbital parameters¶
Get an instance of the Rubin TAP service client, and assert that it exists.
service = get_tap_service("ssotap")
assert service is not None
Query the DP0.3 catalogs, joining the MPCORB and SSObject tables on their ssObjectId. Limit the query to select sources in the MPCORB table with perihelia $q$ < 1.3 au, eccentricities $e$ < 1, and semimajor axes $a$ < 4 au to get only near-Earth objects, excluding interstellar objects with $e$ > 1 or $a$ < 0.
Define the table returned by this query as "uniqueNEOs" since it contains the IDs of unique solar system objects.
q_select = '1.3'
e_select = '1.0'
a_select = '4.0'
query = """
SELECT
mpc.ssObjectId, mpc.mpcDesignation,
mpc.e, mpc.q, mpc.incl
FROM
dp03_catalogs_10yr.MPCORB as mpc
INNER JOIN dp03_catalogs_10yr.SSObject as sso
ON mpc.ssObjectId = sso.ssObjectId
WHERE mpc.q < {} AND mpc.e < {}
AND mpc.q/(1.0-mpc.e) < {} ORDER by mpc.mpcDesignation
""".format(q_select, e_select, a_select)
uniqueNEOs = service.search(query).to_table().to_pandas()
Calculate the semimajor axis $a$ of each object's orbit, using the function defined above, and add it as a column to the uniqueNEOs
table.
a = calc_semimajor_axis(uniqueNEOs['q'], uniqueNEOs['e'])
uniqueNEOs['a'] = a
Then use the function defined above to calculate the aphelion distance $Q$ of each object's orbit and add it as a column to the uniqueNEOs
table.
Q = calc_aphelion(uniqueNEOs['q'], uniqueNEOs['e'])
uniqueNEOs['Q'] = Q
Print the uniqueNEOs
table.
uniqueNEOs
ssObjectId | mpcDesignation | e | q | incl | a | Q | |
---|---|---|---|---|---|---|---|
0 | 3351269693330531197 | 1929 SH | 0.396081 | 1.123543 | 8.450610 | 1.860420 | 2.597297 |
1 | -5234750409166262016 | 1932 EA1 | 0.435987 | 1.080947 | 11.883250 | 1.916528 | 2.752110 |
2 | 7991128850154218427 | 1936 CA | 0.763999 | 0.441069 | 1.321700 | 1.868924 | 3.296779 |
3 | -7789613295760699323 | 1937 UB | 0.623179 | 0.622294 | 6.067580 | 1.651432 | 2.680570 |
4 | 8551587316774204226 | 1947 XC | 0.712520 | 0.625595 | 2.521620 | 2.176135 | 3.726674 |
... | ... | ... | ... | ... | ... | ... | ... |
39465 | -3152111153344676727 | iso00020 | 0.912171 | 0.069097 | 155.764145 | 0.786725 | 1.504354 |
39466 | -5397621441865963928 | iso00026 | 0.921228 | 0.072821 | 109.157257 | 0.924457 | 1.776092 |
39467 | 4406479866270187902 | iso00075 | 0.975119 | 0.087357 | 22.231391 | 3.510922 | 6.934487 |
39468 | 7151227162020825803 | iso00094 | 0.967251 | 0.085642 | 48.439490 | 2.615082 | 5.144522 |
39469 | 2545331322875812297 | iso00116 | 0.924412 | 0.067106 | 119.936046 | 0.887787 | 1.708469 |
39470 rows × 7 columns
2.2 Create semimajor axis $a$ versus eccentricity $e$ plots of the four dynamical sub-populations of NEOs¶
Plot the semimajor axis versus eccentricity of all NEOs in the uniqueNEOs
table.
fig, ax = plt.subplots()
plt.xlim([0., 4.])
plt.ylim([0., 1.])
ax.scatter(uniqueNEOs.a, uniqueNEOs.e, s=0.1)
ax.set_xlabel('semimajor axis (au)')
ax.set_ylabel('eccentricity')
ax.minorticks_on()
plt.show()
In order to show the boundaries for the four NEO sub-populations (Amors, Apollos, Atens, and Atiras), arrays of ($a$, $e$) values along the Earth's perihelion and aphelion distances are needed along with those for the $q$ < 1.3 au NEO boundary.
a_Q_Earth, e_Q_Earth = get_Q_at_planets_q_curve_values(q_Earth)
a_q_Earth, e_q_Earth = get_q_at_planets_Q_curve_values(Q_Earth)
a_q_NEO, e_q_NEO = get_q_at_planets_Q_curve_values(q_NEOs)
Plot the Earth-crossing curves as a function of $a$ and $e$ and label the NEO class regions.
fig, ax = plt.subplots()
plt.xlim([0., 4.])
plt.ylim([0., 1.])
ax.axvline(x=1.0, color="blue")
ax.plot(a_Q_Earth, e_Q_Earth, "blue")
ax.plot(a_q_Earth, e_q_Earth, "blue")
ax.plot(a_q_NEO, e_q_NEO, "black", linewidth=3)
ax.annotate('Amors', xy=(2.25, 0.5), xytext=(2.25, 0.5),
rotation=30.0, weight='bold')
ax.annotate('Apollos', xy=(1.45, 0.75), xytext=(1.45, 0.75),
weight='bold')
ax.annotate('Atens', xy=(0.7, 0.6), xytext=(0.7, 0.6),
rotation=270.0, weight='bold')
ax.annotate('Atiras', xy=(0.3, 0.5), xytext=(0.3, 0.5),
rotation=280.0, weight='bold')
ax.minorticks_on()
plt.xlabel("semimajor axis (au)")
plt.ylabel("eccentricity")
plt.title("NEO Class Boundaries")
plt.show()
To better understand the above ($a$, $e$) plot, remember that the Sun is at the origin ($a$, $e$) = (0, 0) and the Earth has $a$ = 1.0 au and $e$ = 0.017, so it sits on the x-axis where the three blue curves converge. The space between the two outermost blue curves is Earth-crossing space where the Earth-crossing NEOs (Apollos and Atens) sit. A semimajor axis $a$ = 1.0 au divides the Earth-crossing NEOs into Apollos ($a$ > 1.0 au) and Atens ($a$ < 1.0 au). The black curve marks the $q$ < 1.3 au boundary for NEOs by definition. Amors sit between the $q$ < 1.3 au curve and the outer edge of Earth-crossing space (i.e., perihelia > Earth's aphelion = $q$ > 1.017 au); Amors thus always remain farther from the Sun than the Earth. Atiras are opposite to the Amors in that they always remain closer to the Sun than the Earth and live interior to Earth-crossing space; Atiras thus have aphelia < Earth's perihelion = $Q$ < 0.983 au.
Next, in order to plot the four NEO sub-populations on an ($a$, $e$) plot like the one above, first define the parameters of each sub-population needed to extract them individually from the uniqueNEOs
table.
To ensure that we don't miss any NEOs in our table by our defined parameters, total the number of objects in each extracted sub-population and assert that it equals the number of objects in the uniqueNEOs
table.
Amor_params = (uniqueNEOs.q < q_NEOs) & (uniqueNEOs.q > Q_Earth)
Apollo_params = (uniqueNEOs.q < Q_Earth) & (uniqueNEOs.a > 1.0)
Aten_params = (uniqueNEOs.Q > q_Earth) & (uniqueNEOs.a < 1.0)
Atira_params = (uniqueNEOs.Q < q_Earth)
total_NEOs = len(uniqueNEOs[Amor_params]
+ uniqueNEOs[Apollo_params]
+ uniqueNEOs[Aten_params]
+ uniqueNEOs[Atira_params])
assert total_NEOs == len(uniqueNEOs)
Plot the semimajor axis versus eccentricity of all NEOs in the uniqueNEOs
table divided into the four dynamical sub-populations, overplot the boundaries for each sub-population, and label each NEO class region.
fig, ax = plt.subplots()
plt.xlim([0., 4.])
plt.ylim([0., 1.])
ax.scatter(uniqueNEOs[Amor_params].a, uniqueNEOs[Amor_params].e,
s=0.1, color='red')
ax.scatter(uniqueNEOs[Apollo_params].a, uniqueNEOs[Apollo_params].e,
s=0.1, color='green')
ax.scatter(uniqueNEOs[Aten_params].a, uniqueNEOs[Aten_params].e,
s=0.1, color='orange')
ax.scatter(uniqueNEOs[Atira_params].a, uniqueNEOs[Atira_params].e,
s=0.25, color='purple')
ax.plot(a_Q_Earth, e_Q_Earth, "blue")
ax.plot(a_q_Earth, e_q_Earth, "blue")
ax.axvline(x=1.0, color="blue")
ax.plot(a_q_NEO, e_q_NEO, "black", linewidth=5)
ax.annotate('Amors', xy=(2.25, 0.5), xytext=(2.25, 0.5),
rotation=30.0, weight='bold')
ax.annotate('Apollos', xy=(1.45, 0.75), xytext=(1.45, 0.75),
weight='bold')
ax.annotate('Atens', xy=(0.7, 0.6), xytext=(0.7, 0.6),
rotation=270.0, weight='bold')
ax.annotate('Atiras', xy=(0.3, 0.5), xytext=(0.3, 0.5),
rotation=280.0, weight='bold')
ax.minorticks_on()
plt.xlabel("semimajor axis (au)")
plt.ylabel("eccentricity")
plt.title("NEOs in DP0.3")
plt.show()