Skip to content

Interplanetary Trajectories

pg_orbit includes a Lambert solver for computing ballistic transfer orbits between any two planets. Given a departure body, arrival body, departure time, and arrival time, the solver returns the transfer orbit’s energy characteristics: departure C3, arrival C3, v-infinity, time of flight, and transfer semi-major axis. The function processes about 800,000 solutions per second, which means pork chop plots — the standard visualization for launch window analysis — become SQL CROSS JOINs.

Interplanetary trajectory design is one of the more specialized areas of orbital mechanics:

  • GMAT (General Mission Analysis Tool): NASA’s open-source mission design software. Full-featured but steep learning curve. GUI-driven, script-extensible, not designed for batch parameter sweeps.
  • NASA Trajectory Browser: Web-based tool for browsing pre-computed transfer opportunities. Limited to pre-defined targets and time windows.
  • poliastro (Python): Astrodynamics library with Lambert solvers, orbit plotting, and planetary position computation. Good for one-off analysis; batch sweeps require writing loops.
  • STK/Astrogator: Commercial tool with advanced trajectory design. Expensive, steep learning curve.

For all of these, the workflow is: pick a departure date, pick an arrival date, run the solver, record the result. To build a pork chop plot, you sweep a grid of departure and arrival dates and collect results. In Python, this means nested loops. In GMAT, this means scripted batch runs.

Two functions handle the complete Lambert problem:

FunctionReturnsUse case
lambert_transfer(dep_id, arr_id, dep_time, arr_time)RECORD with 6 fieldsFull transfer orbit characterization
lambert_c3(dep_id, arr_id, dep_time, arr_time)float8Departure C3 only (for pork chop plots)

The lambert_transfer() output fields:

FieldUnitsMeaning
c3_departurekm^2/s^2Launch energy: the kinetic energy per unit mass above escape velocity at departure
c3_arrivalkm^2/s^2Arrival energy: excess velocity squared at the target planet
v_inf_departurekm/sHyperbolic excess speed at departure (sqrt of C3)
v_inf_arrivalkm/sHyperbolic excess speed at arrival
tof_daysdaysTime of flight
transfer_smaAUSemi-major axis of the transfer ellipse

Body IDs match the VSOP87 convention: 1=Mercury, 2=Venus, 3=Earth, 4=Mars, 5=Jupiter, 6=Saturn, 7=Uranus, 8=Neptune. The departure body is almost always Earth (3), but the solver works for any planet-to-planet combination.

  • No gravity assists. Voyager, Cassini, and New Horizons all used flyby maneuvers to gain velocity from intermediate planets. The Lambert solver computes direct transfers only.
  • No low-thrust trajectories. Ion drives and solar sails produce continuous thrust. Lambert assumes an instantaneous departure burn and coast to arrival.
  • No three-body effects. The solver uses heliocentric two-body mechanics. Sphere-of-influence transitions, Lagrange point dynamics, and lunar gravity assists are not modeled.
  • No launch vehicle constraints. The solver returns C3, which determines the required launch energy. Mapping C3 to a specific rocket’s payload capacity is a separate analysis.
  • No aerocapture or entry design. The arrival C3 determines how much delta-v is needed for orbit insertion, but pg_orbit does not compute the insertion burn itself.

For mission design beyond first-order feasibility analysis, use GMAT or poliastro with patched-conic or N-body propagation.

The classic trajectory design problem. A 2026 departure window:

SELECT round(c3_departure::numeric, 2) AS c3_dep_km2s2,
round(c3_arrival::numeric, 2) AS c3_arr_km2s2,
round(v_inf_departure::numeric, 2) AS vinf_dep_kms,
round(v_inf_arrival::numeric, 2) AS vinf_arr_kms,
round(tof_days::numeric, 0) AS flight_days,
round(transfer_sma::numeric, 4) AS sma_au
FROM lambert_transfer(
3, 4, -- Earth to Mars
'2026-05-01 00:00:00+00'::timestamptz, -- departure
'2027-01-15 00:00:00+00'::timestamptz -- arrival
);

For a typical Earth-Mars transfer, expect departure C3 in the 8-20 km^2/s^2 range, flight times of 200-300 days, and a transfer SMA of roughly 1.2-1.5 AU.

Venus transfers require less energy than Mars because Venus is closer to the Sun:

SELECT round(c3_departure::numeric, 2) AS c3_dep,
round(tof_days::numeric, 0) AS flight_days,
round(transfer_sma::numeric, 4) AS sma_au
FROM lambert_transfer(
3, 2, -- Earth to Venus
'2026-06-01 00:00:00+00'::timestamptz,
'2026-10-15 00:00:00+00'::timestamptz
);

Typical Earth-Venus C3 is 5-15 km^2/s^2 with flight times of 100-200 days.

A direct ballistic transfer to Jupiter requires significant energy:

SELECT round(c3_departure::numeric, 0) AS c3_dep,
round(tof_days::numeric, 0) AS flight_days
FROM lambert_transfer(
3, 5, -- Earth to Jupiter
'2026-01-01 00:00:00+00'::timestamptz,
'2028-06-01 00:00:00+00'::timestamptz
);

Expect C3 in the 70-100+ km^2/s^2 range. This is why real Jupiter missions use Venus and Earth gravity assists to reduce the required launch energy.

When you only need the departure energy and not the full transfer characterization:

SELECT round(lambert_c3(3, 4,
'2026-05-01 00:00:00+00'::timestamptz,
'2027-01-15 00:00:00+00'::timestamptz)::numeric, 2) AS c3;

lambert_c3() returns just the departure C3 as a single float. It is faster than lambert_transfer() when you do not need the other output fields.

A pork chop plot shows departure C3 as a function of departure date and arrival date. This is the fundamental tool for launch window analysis. Generate one in SQL with a CROSS JOIN:

SELECT dep::date AS departure,
arr::date AS arrival,
round(lambert_c3(3, 4, dep, arr)::numeric, 1) AS c3
FROM generate_series(
'2026-04-01'::timestamptz,
'2026-06-01'::timestamptz,
interval '10 days'
) AS dep
CROSS JOIN generate_series(
'2027-01-01'::timestamptz,
'2027-03-01'::timestamptz,
interval '10 days'
) AS arr
ORDER BY c3;

The lowest C3 values correspond to the optimal departure/arrival combination. This mini grid has about 40 points; a real pork chop plot uses finer resolution.

For a publication-quality pork chop plot, use 1-day resolution over a wide window:

SELECT dep::date AS departure,
arr::date AS arrival,
round(lambert_c3(3, 4, dep, arr)::numeric, 2) AS c3
FROM generate_series(
'2026-01-01'::timestamptz,
'2026-06-01'::timestamptz,
interval '1 day'
) AS dep
CROSS JOIN generate_series(
'2026-09-01'::timestamptz,
'2027-06-01'::timestamptz,
interval '1 day'
) AS arr;

This generates approximately 150 x 270 = 40,500 transfer solutions. At 800,000 solutions per second, the query completes in well under a second. The result is a table you can feed into any contour plot library.

Look at multiple departure windows side by side:

WITH windows AS (
SELECT '2026 window' AS window, '2026-05-01'::timestamptz AS dep, '2027-01-15'::timestamptz AS arr
UNION ALL
SELECT '2028 window', '2028-10-01', '2029-06-15'
UNION ALL
SELECT '2031 window', '2031-02-01', '2031-10-01'
)
SELECT window,
dep::date AS departure,
arr::date AS arrival,
round(c3_departure::numeric, 2) AS c3_dep,
round(c3_arrival::numeric, 2) AS c3_arr,
round(tof_days::numeric, 0) AS flight_days,
round(transfer_sma::numeric, 4) AS sma_au
FROM windows,
LATERAL lambert_transfer(3, 4, dep, arr);

Earth-Mars launch windows repeat approximately every 26 months (the synodic period). Some windows are more favorable than others because Mars’s orbit is significantly eccentric.

Use a fine sweep over departure dates with a fixed arrival date to find the minimum C3:

SELECT dep::date AS departure,
round(lambert_c3(3, 4, dep,
'2027-01-15 00:00:00+00'::timestamptz)::numeric, 2) AS c3
FROM generate_series(
'2026-03-01'::timestamptz,
'2026-08-01'::timestamptz,
interval '1 day'
) AS dep
ORDER BY lambert_c3(3, 4, dep,
'2027-01-15 00:00:00+00'::timestamptz)
LIMIT 10;

The top 10 rows show the departure dates with the lowest launch energy for a fixed arrival on 2027-01-15. In practice, you would sweep both departure and arrival together (the full pork chop plot), but fixing one dimension is useful for understanding the sensitivity.

Venus-Mars via Earth (multi-leg comparison)

Section titled “Venus-Mars via Earth (multi-leg comparison)”

While the Lambert solver does not compute multi-leg gravity assists directly, you can compare the energy requirements of each leg independently:

-- Earth to Venus (leg 1)
SELECT 'Earth-Venus' AS leg,
round(c3_departure::numeric, 2) AS c3_dep,
round(tof_days::numeric, 0) AS flight_days
FROM lambert_transfer(3, 2,
'2026-06-01'::timestamptz, '2026-10-15'::timestamptz)
UNION ALL
-- Earth to Mars (direct)
SELECT 'Earth-Mars (direct)',
round(c3_departure::numeric, 2),
round(tof_days::numeric, 0)
FROM lambert_transfer(3, 4,
'2026-05-01'::timestamptz, '2027-01-15'::timestamptz)
UNION ALL
-- Earth to Jupiter (direct)
SELECT 'Earth-Jupiter (direct)',
round(c3_departure::numeric, 0),
round(tof_days::numeric, 0)
FROM lambert_transfer(3, 5,
'2026-01-01'::timestamptz, '2028-06-01'::timestamptz);

This shows why gravity assists exist: the direct Earth-Jupiter C3 is many times higher than the Earth-Venus C3. By flying to Venus first and using its gravity to redirect, missions can reach Jupiter with far less launch energy.

Verify the solver produces physically reasonable results:

-- C3 should be positive and less than 200 for any Earth-Mars transfer
SELECT lambert_c3(3, 4,
'2026-05-01'::timestamptz,
'2027-01-15'::timestamptz) > 0 AS positive,
lambert_c3(3, 4,
'2026-05-01'::timestamptz,
'2027-01-15'::timestamptz) < 200 AS reasonable;
-- Transfer SMA should be between Earth and Mars orbits
SELECT transfer_sma > 0.8 AS above_venus,
transfer_sma < 5.0 AS below_jupiter
FROM lambert_transfer(3, 4,
'2026-05-01'::timestamptz,
'2027-01-15'::timestamptz);