Creating a schematic of spacecraft orbits in Mercury’s magnetosphere#

In this example, we talk through how create a to-scale schematic of Mercury’s magnetosphere, inlcuding average boundary locations, and sample orbits from both MESSENGER and predictions from BepiColombo’s MPO and Mio.

import matplotlib as mpl
import matplotlib.pyplot as plt
import spiceypy as spice
from matplotlib.patches import Circle
from sunpy.time import TimeRange

from hermpy.net import ClientSPICE
from hermpy.plotting import plot_magnetospheric_boundaries
from hermpy.utils import Constants as c

# Controls the plot border width
mpl.rcParams["axes.linewidth"] = 2

We start by outlining some times from which to fetch orbits. Here we use TimeRange from sunpy.

bepi_time_range = TimeRange("2026-08-15", "2026-08-16")
messenger_time_range = TimeRange("2012-08-05", "2012-08-06")

We use hermpy.net.ClientSPICE to aid in the downloading a querying of SPICE kernels. We designate specific SPICE kernels to download which we require to get positions for BepiColombo’s MPO and Mio, along with MESSENGER. See here for more details.

spice_client = ClientSPICE()
spice_client.KERNEL_LOCATIONS.update(
    {
        "MESSENGER": {
            "BASE": "https://naif.jpl.nasa.gov/pub/naif/",
            "DIRECTORY": "pds/data/mess-e_v_h-spice-6-v1.0/messsp_1000/data/spk/",
            "PATTERNS": ["msgr_??????_??????_??????_od431sc_2.bsp"],
        },
        "BepiColombo": {
            "BASE": "http://spiftp.esac.esa.int/data/SPICE/BEPICOLOMBO/",
            "DIRECTORY": "kernels/spk/",
            "PATTERNS": [
                "de432s.bsp",
                "bc_sci_v02.bsp",
                "bc_mpo_mlt_50037_20260314_20280529_v05.bsp",
                "bc_mmo_mlt_50038_20251220_20280305_v05.bsp",
            ],
        },
        "BepiColombo Frames": {
            "BASE": "http://spiftp.esac.esa.int/data/SPICE/BEPICOLOMBO/",
            "DIRECTORY": "kernels/fk/",
            "PATTERNS": [
                "bc_sci_v12.tf",
            ],
        },
    }
)

The time ranges defined earlier are used to query the positions for those times in the Mercury-Solar-Magnetospheric (MSM) frame. Those postions are divided by Mercury’s radius (hermpy.utils.Constants) so our plot will be in units of Mercury radii.

with spice_client.KernelPool():
    bepi_times = [t.center.to_datetime() for t in bepi_time_range.split(1000)]
    bepi_ets = spice.datetime2et(bepi_times)

    mpo_positions, _ = spice.spkpos("MPO", bepi_ets, "BC_MSO", "NONE", "MERCURY")
    mpo_positions /= c.MERCURY_RADIUS.to("km").value
    mpo_positions -= c.DIPOLE_OFFSET_RADII.value

    mmo_positions, _ = spice.spkpos("MMO", bepi_ets, "BC_MSO", "NONE", "MERCURY")
    mmo_positions /= c.MERCURY_RADIUS.to("km").value
    mmo_positions -= c.DIPOLE_OFFSET_RADII.value

    messenger_times = [t.center.to_datetime() for t in messenger_time_range.split(1000)]
    messenger_ets = spice.datetime2et(messenger_times)

    messenger_positions, _ = spice.spkpos(
        "MESSENGER", messenger_ets, "BC_MSO", "NONE", "MERCURY"
    )
    messenger_positions /= c.MERCURY_RADIUS.to("km").value
    messenger_positions -= c.DIPOLE_OFFSET_RADII.value
Files Downloaded:   0%|          | 0/12 [00:00<?, ?file/s]
Files Downloaded:   8%|▊         | 1/12 [00:00<00:02,  4.05file/s]
Files Downloaded:  67%|██████▋   | 8/12 [00:00<00:00, 19.41file/s]

bc_sci_v02.bsp:   0%|          | 0.00/6.14k [00:00<?, ?B/s]




de432s.bsp:   0%|          | 0.00/10.9M [00:00<?, ?B/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   0%|          | 0.00/33.0M [00:00<?, ?B/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:   0%|          | 0.00/8.40M [00:00<?, ?B/s]

bc_sci_v02.bsp:  17%|█▋        | 1.02k/6.14k [00:00<00:00, 9.22kB/s]




de432s.bsp:   0%|          | 1.02k/10.9M [00:00<27:24, 6.62kB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   0%|          | 1.02k/33.0M [00:00<59:38, 9.22kB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:   0%|          | 1.02k/8.40M [00:00<15:14, 9.19kB/s]

bc_sci_v02.bsp:  57%|█████▋    | 3.48k/6.14k [00:00<00:00, 17.2kB/s]




de432s.bsp:   0%|          | 5.08k/10.9M [00:00<08:13, 22.0kB/s]




bc_mmo_mlt_50038_20251220_20280305_v05.bsp:   0%|          | 5.08k/8.40M [00:00<05:22, 26.0kB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   0%|          | 5.08k/33.0M [00:00<21:32, 25.5kB/s]




de432s.bsp:   0%|          | 22.1k/10.9M [00:00<02:19, 78.0kB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:   0%|          | 23.5k/8.40M [00:00<01:30, 92.6kB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   0%|          | 22.1k/33.0M [00:00<06:28, 84.9kB/s]




de432s.bsp:   1%|          | 54.5k/10.9M [00:00<01:08, 159kB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:   1%|          | 60.1k/8.40M [00:00<00:43, 191kB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   0%|          | 54.5k/33.0M [00:00<03:15, 168kB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:   1%|▏         | 114k/8.40M [00:00<00:27, 303kB/s]




de432s.bsp:   1%|          | 107k/10.9M [00:00<00:39, 270kB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   0%|          | 103k/33.0M [00:00<02:03, 267kB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:   2%|▏         | 190k/8.40M [00:00<00:18, 440kB/s]




de432s.bsp:   1%|▏         | 161k/10.9M [00:00<00:31, 344kB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   0%|          | 160k/33.0M [00:00<01:32, 356kB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:   3%|▎         | 293k/8.40M [00:00<00:13, 612kB/s]




de432s.bsp:   2%|▏         | 244k/10.9M [00:00<00:22, 474kB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   1%|          | 249k/33.0M [00:00<01:04, 505kB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:   5%|▌         | 424k/8.40M [00:00<00:09, 806kB/s]




de432s.bsp:   3%|▎         | 344k/10.9M [00:00<00:17, 613kB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   1%|          | 364k/33.0M [00:00<00:48, 678kB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:   7%|▋         | 589k/8.40M [00:00<00:07, 1.03MB/s]




de432s.bsp:   4%|▍         | 469k/10.9M [00:01<00:13, 772kB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   2%|▏         | 509k/33.0M [00:00<00:36, 879kB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:   9%|▉         | 786k/8.40M [00:01<00:05, 1.28MB/s]




de432s.bsp:   6%|▌         | 620k/10.9M [00:01<00:10, 958kB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   2%|▏         | 684k/33.0M [00:01<00:29, 1.10MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  12%|█▏        | 1.02M/8.40M [00:01<00:04, 1.57MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   3%|▎         | 862k/33.0M [00:01<00:25, 1.27MB/s]




de432s.bsp:   7%|▋         | 802k/10.9M [00:01<00:08, 1.17MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  15%|█▌        | 1.30M/8.40M [00:01<00:03, 1.88MB/s]




de432s.bsp:   9%|▉         | 1.02M/10.9M [00:01<00:06, 1.42MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   3%|▎         | 1.11M/33.0M [00:01<00:20, 1.56MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  20%|█▉        | 1.64M/8.40M [00:01<00:02, 2.27MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   4%|▍         | 1.39M/33.0M [00:01<00:16, 1.88MB/s]




de432s.bsp:  11%|█▏        | 1.25M/10.9M [00:01<00:06, 1.60MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  25%|██▍       | 2.06M/8.40M [00:01<00:02, 2.77MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   5%|▌         | 1.72M/33.0M [00:01<00:14, 2.23MB/s]




de432s.bsp:  14%|█▍        | 1.55M/10.9M [00:01<00:04, 1.95MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  30%|███       | 2.56M/8.40M [00:01<00:01, 3.33MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   6%|▋         | 2.10M/33.0M [00:01<00:11, 2.60MB/s]




de432s.bsp:  18%|█▊        | 1.92M/10.9M [00:01<00:03, 2.37MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  37%|███▋      | 3.14M/8.40M [00:01<00:01, 3.97MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   8%|▊         | 2.55M/33.0M [00:01<00:10, 3.04MB/s]




de432s.bsp:  22%|██▏       | 2.35M/10.9M [00:01<00:03, 2.82MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  45%|████▌     | 3.81M/8.40M [00:01<00:00, 4.64MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:   9%|▉         | 3.08M/33.0M [00:01<00:08, 3.59MB/s]




de432s.bsp:  26%|██▋       | 2.86M/10.9M [00:01<00:02, 3.39MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  54%|█████▍    | 4.54M/8.40M [00:01<00:00, 5.31MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  11%|█▏        | 3.72M/33.0M [00:01<00:06, 4.27MB/s]




de432s.bsp:  32%|███▏      | 3.48M/10.9M [00:02<00:01, 4.04MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  63%|██████▎   | 5.26M/8.40M [00:02<00:00, 5.73MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  14%|█▎        | 4.50M/33.0M [00:02<00:05, 5.15MB/s]




de432s.bsp:  38%|███▊      | 4.19M/10.9M [00:02<00:01, 4.76MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  70%|███████   | 5.89M/8.40M [00:02<00:00, 5.77MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  16%|█▋        | 5.42M/33.0M [00:02<00:04, 6.12MB/s]




de432s.bsp:  46%|████▌     | 5.00M/10.9M [00:02<00:01, 5.54MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  77%|███████▋  | 6.50M/8.40M [00:02<00:00, 5.75MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  20%|█▉        | 6.46M/33.0M [00:02<00:03, 7.14MB/s]




de432s.bsp:  54%|█████▍    | 5.91M/10.9M [00:02<00:00, 6.35MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  84%|████████▍ | 7.08M/8.40M [00:02<00:00, 5.60MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  23%|██▎       | 7.63M/33.0M [00:02<00:03, 8.21MB/s]




de432s.bsp:  62%|██████▏   | 6.73M/10.9M [00:02<00:00, 6.87MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  27%|██▋       | 8.90M/33.0M [00:02<00:02, 9.47MB/s]




de432s.bsp:  69%|██████▊   | 7.49M/10.9M [00:02<00:00, 6.72MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  31%|███       | 10.3M/33.0M [00:02<00:02, 10.1MB/s]




de432s.bsp:  76%|███████▌  | 8.23M/10.9M [00:02<00:00, 6.70MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  91%|█████████ | 7.64M/8.40M [00:02<00:00, 3.52MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  36%|███▌      | 11.9M/33.0M [00:02<00:01, 11.6MB/s]




de432s.bsp:  82%|████████▏ | 8.90M/10.9M [00:02<00:00, 6.41MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  42%|████▏     | 13.8M/33.0M [00:02<00:01, 13.2MB/s]




de432s.bsp:  88%|████████▊ | 9.54M/10.9M [00:02<00:00, 6.25MB/s]


bc_mmo_mlt_50038_20251220_20280305_v05.bsp:  96%|█████████▌| 8.09M/8.40M [00:02<00:00, 2.96MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  48%|████▊     | 15.8M/33.0M [00:02<00:01, 14.7MB/s]




de432s.bsp:  93%|█████████▎| 10.2M/10.9M [00:03<00:00, 6.02MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  54%|█████▍    | 18.0M/33.0M [00:03<00:00, 16.4MB/s]







de432s.bsp:  99%|█████████▉| 10.8M/10.9M [00:03<00:00, 5.29MB/s]





Files Downloaded:  92%|█████████▏| 11/12 [00:03<00:00,  2.42file/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  62%|██████▏   | 20.4M/33.0M [00:03<00:00, 18.2MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  68%|██████▊   | 22.4M/33.0M [00:03<00:00, 18.2MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  74%|███████▍  | 24.5M/33.0M [00:03<00:00, 18.5MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  80%|███████▉  | 26.4M/33.0M [00:03<00:00, 16.8MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  85%|████████▌ | 28.1M/33.0M [00:03<00:00, 16.4MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  90%|█████████ | 29.7M/33.0M [00:03<00:00, 16.1MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  95%|█████████▍| 31.3M/33.0M [00:04<00:00, 10.1MB/s]





bc_mpo_mlt_50037_20260314_20280529_v05.bsp:  99%|█████████▉| 32.6M/33.0M [00:04<00:00, 8.34MB/s]






Files Downloaded: 100%|██████████| 12/12 [00:04<00:00,  2.48file/s]

We create our plot, and begin by plotting the above positions. The are in the form of a 2D array, with each column corresopnding to x, y, and z respectively. We define some settings for the axis here as well, to centre the orbits in the panel.

We add labels using matplotlib’s builtin ax.text, and position them manually.

hermpy.plotting.plot_magnetospheric_boundaries can be used to add average bow shock and magnetopause boundary locations to the axis, as determined by Winslow et al. (2013) [link].

Finally, we use matplotlib’s Circle patch to draw Mercury. In the MSM coordinate system, Mercury’s geographical centre is offset from the centre of the coordinate system: the dipole.

fig, ax = plt.subplots()

ax.set(
    xlim=(-4, 6),
    ylim=(-6, 4),
    xticks=[],
    yticks=[],
    aspect="equal",
)

spacecraft_params = {
    "lw": 3,
}
ax.plot(
    mpo_positions[:, 0],  # X
    mpo_positions[:, 2],  # Z
    color="blue",
    **spacecraft_params,
)
ax.plot(
    mmo_positions[:, 0],
    mmo_positions[:, 2],
    color="red",
    **spacecraft_params,
)
ax.plot(
    messenger_positions[:, 0],
    messenger_positions[:, 2],
    color="orange",
    zorder=0,
    **spacecraft_params,
)

ax.text(1.5, 0.8, "MPO", color="blue", weight="bold")
ax.text(4.5, 0, "Mio", color="red", weight="bold")
ax.text(0.8, -5, "MESSENGER", color="orange", weight="bold")

# WARNING: This function will likely change in future versions
plot_magnetospheric_boundaries(ax, "xz", lw=2, zorder=-1)

mercury_circle = Circle(
    (0, -c.DIPOLE_OFFSET_RADII.value),
    radius=1,
    linewidth=3,
    facecolor="lightgrey",
    edgecolor="black",
)

ax.add_artist(mercury_circle)

plt.show()
mercury schematic

Total running time of the script: (0 minutes 10.274 seconds)

Gallery generated by Sphinx-Gallery