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
- Open the JPL Horizons Web Interface.
- 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 |
- Generate and download the ephemeris table, then delete all header and footer text so only the comma-separated rows between
$$SOEand$$EOEremain.
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, andTable Settings). - Callouts: (1)
Vector Tableselection, (2)@399/@10center code, (3)km&km/sCSV 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
- Type 9 (Lagrange Interpolation): Best for discrete state vectors exported from JPL Horizons.
- Custom NAIF ID: Use a large negative integer (e.g.,
-999001) to avoid colliding with official NASA/ESA mission IDs. - Center Body ID:
399= Earth,10= Sun,499= Mars. Must match your Horizons coordinate center. - 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
SKIPtokens toDATA_ORDER. - Polynomial Degree:
3(cubic) is standard; ensure your CSV contains at leastPOLYNOM_DEGREE + 1rows.
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.