Skip to content

Custom .bsp Pipeline (mkspk)

When an official NAIF .bsp kernel does not exist—or when you want to visualize a custom spacecraft trajectory from state vectors—you can generate your own binary SPICE kernel using JPL Horizons and NAIF's mkspk utility.


Pipeline Overview

JPL Horizons (State Vectors) ──► Cleaned CSV ──► mkspk.exe (+ Setup File) ──► custom.bsp ──► OpenSpace .asset

Step 1: Export State Vectors from JPL Horizons

  1. Open the JPL Horizons Web Interface.
  2. Configure the ephemeris parameters:
Parameter Setting Notes
Ephemeris Type Vector Table Required for Cartesian position/velocity state vectors
Target Body Your target spacecraft / body Note the NAIF ID (or assign a custom negative ID)
Coordinate Center @10 (Sun) or @399 (Earth) Must match CENTER_ID in your mkspk setup file
Time Specification Start, Stop, and Step size Shorter steps (e.g., 1 h) produce smoother curves
Table Settings Output units: km and km/s Select **State vector {x, y, z, vx, vy, vz}** + CSV format
  1. Generate and download the ephemeris table, then delete all header and footer text so only the comma-separated rows between $$SOE and $$EOE remain.

Screenshot Placeholder: horizons-vector-settings.png

  • Crop: Capture the JPL Horizons main parameter form highlighting the 5 required fields (Ephemeris Type, Target Body, Coordinate Center, Time Specification, and Table Settings).
  • Callouts: (1) Vector Table selection, (2) @399 / @10 center code, (3) km & km/s CSV table output option.

Step 2: Configure the mkspk Setup File

Create a plain-text configuration file named trajectory.setup alongside your cleaned CSV data and NAIF leapseconds kernel (naif0012.tls). Click the + icons below for parameter rules:

\begindata

INPUT_DATA_TYPE   = 'STATES'
OUTPUT_SPK_TYPE   = 9                      # (1)!
OBJECT_ID         = -999001                # (2)!
CENTER_ID         = 399                    # (3)!
REF_FRAME_NAME    = 'J2000'
PRODUCER_ID       = 'OpenSpace Workshop'
DATA_ORDER        = 'EPOCH X Y Z VX VY VZ' # (4)!
DATA_DELIMITER    = ','
LINES_PER_RECORD  = 1
TIME_WRAPPER      = '# TDB'
LEAPSECONDS_FILE  = 'naif0012.tls'
INPUT_DATA_FILE   = 'horizons_vectors.csv'
OUTPUT_SPK_FILE   = 'custom_mission.bsp'
POLYNOM_DEGREE    = 3                      # (5)!
SEGMENT_ID        = 'CUSTOM_MISSION_TRAJ'

\begintext
  1. Type 9 (Lagrange Interpolation): Best for discrete state vectors exported from JPL Horizons.
  2. Custom NAIF ID: Use a large negative integer (e.g., -999001) to avoid colliding with official NASA/ESA mission IDs.
  3. Center Body ID: 399 = Earth, 10 = Sun, 499 = Mars. Must match your Horizons coordinate center.
  4. Column Mapping: Must match the exact column order in your cleaned CSV file. Remove unused columns (such as calendar strings or light-time) or add SKIP tokens to DATA_ORDER.
  5. Polynomial Degree: 3 (cubic) is standard; ensure your CSV contains at least POLYNOM_DEGREE + 1 rows.

Step 3: Compile & Verify the .bsp Kernel

Run mkspk from your terminal, then verify the generated time coverage with spacit:

.\mkspk.exe -setup trajectory.setup
.\spacit.exe
# Choose: S (Summarize SPK file)
# Enter leapseconds kernel: naif0012.tls
# Enter SPK file: custom_mission.bsp
Sample spacit Verification Output
Segment ID     : CUSTOM_MISSION_TRAJ 
Target Body    : Body -999001 
Center Body    : Earth (399) 
Reference Frame: Frame 1 (J2000) 
SPK Data Type  : Type 9 
UTC Start Time : 2026-NOV-01 00:00:00.000 
UTC Stop Time  : 2026-DEC-01 00:00:00.000

Time Window Bounds

OpenSpace will throw a SPICE(SPKINSUFFDATA) error if the simulation clock steps outside the UTC Start Time and UTC Stop Time reported by spacit. Always set your profile's initial time inside this window.


Step 4: Load into an OpenSpace .asset File

Place custom_mission.bsp inside your ${USER}/data/assets/ project folder and reference it with SpiceTranslation:

local assetHelper = asset.require("util/asset_helper")

local customBsp = asset.localResource("custom_mission.bsp")
local lsk = asset.localResource("naif0012.tls")

local TrajectoryNode = {
    Identifier = "CustomMission",
    Parent = "Earth",
    Transform = {
        Translation = {
            Type = "SpiceTranslation",
            Target = "-999001",
            Observer = "EARTH",
            Kernels = { customBsp, lsk }
        }
    },
    Renderable = {
        Type = "RenderableTrailTrajectory",
        Translation = {
            Type = "SpiceTranslation",
            Target = "-999001",
            Observer = "EARTH",
            Kernels = { customBsp, lsk }
        },
        Color = { 0.2, 0.85, 1.0 },
        StartTime = "2026 NOV 01 00:00:00",
        EndTime = "2026 DEC 01 00:00:00",
        SampleInterval = 3600
    },
    GUI = {
        Name = "Custom Mission",
        Path = "/Missions/Custom"
    }
}

asset.onInitialize(function()
    openspace.addSceneGraphNode(TrajectoryNode)
end)

asset.onDeinitialize(function()
    openspace.removeSceneGraphNode(TrajectoryNode)
end)

Forward Slashes Only

If you pass explicit file paths inside .asset files instead of asset.localResource(...), always use forward slashes (C:/OpenSpace/user/...). Windows backslashes (\) act as escape characters in Lua strings and will fail to load.