.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/plot_b_plane.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_plot_b_plane.py: B-Plane Projection ================== When one body approaches another on a hyperbolic trajectory, it follows a curved path that far from the encounter asymptotes to a line. The incoming asymptote is the straight-line path the body would follow if there were no gravity. The B-plane is the plane that passes through the center of the target body and is perpendicular to this incoming asymptote. Imagine looking down the barrel of the incoming trajectory: the B-plane is the target you see, with the target body at the origin. The B-vector points from the center of the target body to the point where the incoming asymptote pierces this plane. Its length is the impact parameter -- the distance by which the body would miss if there were no gravity. A larger B-vector means a wider miss; a B-vector of zero means a head-on collision. The B-plane is split into two axes: - **B dot T**: component along the intersection of the B-plane with the ecliptic. - **B dot R**: component perpendicular to T within the B-plane. Together these give a 2D coordinate for the encounter geometry. This example demonstrates B-plane computation using the 2029 close approach of Apophis to Earth. .. GENERATED FROM PYTHON SOURCE LINES 30-36 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np import kete .. GENERATED FROM PYTHON SOURCE LINES 37-43 Fetch Apophis and find the close approach ------------------------------------------ We fetch the orbit of Apophis from JPL Horizons, then use N-body propagation to find the epoch of closest approach to Earth. The B-plane is evaluated at this epoch. .. GENERATED FROM PYTHON SOURCE LINES 43-57 .. code-block:: Python obj = kete.HorizonsProperties.fetch("Apophis") # Get Earth's state at the object's epoch to use as the second body earth = kete.spice.get_state("Earth", obj.state.jd) # Find the closest approach epoch within a 20-year window jd_start = obj.state.jd jd_end = jd_start + 365.25 * 20 ca_time, ca_dist = kete.closest_approach(obj.state, earth, jd_start, jd_end) print(f"Closest approach: {ca_time.iso}") print(f"Distance: {ca_dist * kete.constants.AU_KM:.0f} km") .. rst-class:: sphx-glr-script-out .. code-block:: none Closest approach: 2029-04-13T21:45:03.631+00:00 Distance: 38011 km .. GENERATED FROM PYTHON SOURCE LINES 58-63 Nominal B-plane ---------------- Propagate the nominal orbit to the close approach epoch, re-center on Earth, and compute the B-plane. .. GENERATED FROM PYTHON SOURCE LINES 63-77 .. code-block:: Python state_ca = kete.propagate_n_body(obj.state, ca_time.jd, non_gravs=[obj.non_grav]) geo_state = state_ca.change_center(399) bp = kete.compute_b_plane(geo_state) print("B-Plane parameters:") print(f" B dot T: {bp.b_t * kete.constants.AU_KM:12.1f} km") print(f" B dot R: {bp.b_r * kete.constants.AU_KM:12.1f} km") print(f" |B|: {bp.b_mag * kete.constants.AU_KM:12.1f} km") print(f" theta: {np.degrees(bp.theta):12.2f} deg") print(f" v_inf: {bp.v_inf * kete.constants.AU_KM / 86400:12.3f} km/s") print(f" Closest approach: {bp.closest_approach * kete.constants.AU_KM:12.1f} km") .. rst-class:: sphx-glr-script-out .. code-block:: none B-Plane parameters: B dot T: 38962.0 km B dot R: 28546.8 km |B|: 48300.7 km theta: 36.23 deg v_inf: 5.841 km/s Closest approach: 38011.5 km .. GENERATED FROM PYTHON SOURCE LINES 78-88 Orbital uncertainty in the B-plane ------------------------------------ A key use of the B-plane is understanding how orbital uncertainty maps onto encounter geometry. We sample the full covariance matrix of the orbit, propagate each sample to the encounter epoch with N-body mechanics, and plot where each lands in the B-plane. This type of analysis is commonly used in planetary defense to visualize how orbital uncertainty translates into encounter geometry. .. GENERATED FROM PYTHON SOURCE LINES 88-122 .. code-block:: Python n_samples = 200 states, non_gravs = obj.sample(n_samples) earth_radius_km = 6371 # Propagate all samples to the close approach epoch propagated = kete.propagate_n_body(states, ca_time.jd, non_gravs=non_gravs) b_t_vals = [] b_r_vals = [] n_impacts = 0 for st in propagated: geo = st.change_center(399) try: bp_sample = kete.compute_b_plane(geo) bt = bp_sample.b_t * kete.constants.AU_KM br = bp_sample.b_r * kete.constants.AU_KM if not (np.isfinite(bt) and np.isfinite(br)): # NaN B-plane likely means a grazing/impact trajectory n_impacts += 1 elif bp_sample.b_mag * kete.constants.AU_KM < earth_radius_km: n_impacts += 1 else: b_t_vals.append(bt) b_r_vals.append(br) except ValueError: # Non-hyperbolic w.r.t. Earth -- count as an impact n_impacts += 1 if n_impacts > 0: print( f"Impact trajectories: {n_impacts} / {n_samples} samples ({100 * n_impacts / n_samples:.1f}%)" ) .. GENERATED FROM PYTHON SOURCE LINES 123-128 Visualize the B-plane --------------------- The nominal encounter point and the cloud of sampled encounters. The spread shows how the current orbital uncertainty maps onto the B-plane. .. GENERATED FROM PYTHON SOURCE LINES 128-163 .. code-block:: Python b_t_arr = np.array(b_t_vals) b_r_arr = np.array(b_r_vals) nom_bt = bp.b_t * kete.constants.AU_KM nom_br = bp.b_r * kete.constants.AU_KM fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5), dpi=250) # Left panel: full view with Earth for scale for ax in (ax1, ax2): ax.scatter(b_t_arr, b_r_arr, s=5, c="steelblue", alpha=0.5, label="Samples") ax.scatter(nom_bt, nom_br, s=100, c="red", marker="*", zorder=5, label="Nominal") ax.set_xlabel("B dot T (km)") ax.set_ylabel("B dot R (km)") ax.set_aspect("equal") ax.legend() ax.grid(True, alpha=0.3) earth_circle = plt.Circle((0, 0), earth_radius_km, color="green", alpha=0.3) ax1.add_patch(earth_circle) ax1.annotate("Earth", (0, 0), ha="center", va="center", fontsize=9, color="darkgreen") ax1.set_title("Apophis 2029 B-Plane") # Right panel: zoomed to the sampled region if len(b_t_arr) > 1: margin = 0.15 span_t = b_t_arr.max() - b_t_arr.min() span_r = b_r_arr.max() - b_r_arr.min() ax2.set_xlim(b_t_arr.min() - margin * span_t, b_t_arr.max() + margin * span_t) ax2.set_ylim(b_r_arr.min() - margin * span_r, b_r_arr.max() + margin * span_r) ax2.set_title("Zoomed to Uncertainty Region") plt.tight_layout() plt.show() .. image-sg:: /auto_examples/images/sphx_glr_plot_b_plane_001.png :alt: Apophis 2029 B-Plane, Zoomed to Uncertainty Region :srcset: /auto_examples/images/sphx_glr_plot_b_plane_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 4.678 seconds) .. _sphx_glr_download_auto_examples_plot_b_plane.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_b_plane.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_b_plane.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_b_plane.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_