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.
How you do it today
Section titled “How you do it today”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.
What changes with pg_orbit
Section titled “What changes with pg_orbit”Two functions handle the complete Lambert problem:
| Function | Returns | Use case |
|---|---|---|
lambert_transfer(dep_id, arr_id, dep_time, arr_time) | RECORD with 6 fields | Full transfer orbit characterization |
lambert_c3(dep_id, arr_id, dep_time, arr_time) | float8 | Departure C3 only (for pork chop plots) |
The lambert_transfer() output fields:
| Field | Units | Meaning |
|---|---|---|
c3_departure | km^2/s^2 | Launch energy: the kinetic energy per unit mass above escape velocity at departure |
c3_arrival | km^2/s^2 | Arrival energy: excess velocity squared at the target planet |
v_inf_departure | km/s | Hyperbolic excess speed at departure (sqrt of C3) |
v_inf_arrival | km/s | Hyperbolic excess speed at arrival |
tof_days | days | Time of flight |
transfer_sma | AU | Semi-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.
What pg_orbit does not replace
Section titled “What pg_orbit does not replace”- 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.
Try it
Section titled “Try it”Earth-Mars transfer
Section titled “Earth-Mars transfer”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_auFROM 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.
Earth-Venus transfer
Section titled “Earth-Venus transfer”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_auFROM 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.
Earth-Jupiter transfer
Section titled “Earth-Jupiter transfer”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_daysFROM 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.
Using lambert_c3 for quick comparisons
Section titled “Using lambert_c3 for quick comparisons”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.
Mini pork chop plot
Section titled “Mini pork chop plot”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 c3FROM generate_series( '2026-04-01'::timestamptz, '2026-06-01'::timestamptz, interval '10 days') AS depCROSS JOIN generate_series( '2027-01-01'::timestamptz, '2027-03-01'::timestamptz, interval '10 days') AS arrORDER 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.
Full pork chop plot: 150x150 grid
Section titled “Full pork chop plot: 150x150 grid”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 c3FROM generate_series( '2026-01-01'::timestamptz, '2026-06-01'::timestamptz, interval '1 day') AS depCROSS 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.
Compare transfer windows
Section titled “Compare transfer windows”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_auFROM 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.
Find the optimal departure date
Section titled “Find the optimal departure date”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 c3FROM generate_series( '2026-03-01'::timestamptz, '2026-08-01'::timestamptz, interval '1 day') AS depORDER 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_daysFROM 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.
Sanity checks
Section titled “Sanity checks”Verify the solver produces physically reasonable results:
-- C3 should be positive and less than 200 for any Earth-Mars transferSELECT 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 orbitsSELECT transfer_sma > 0.8 AS above_venus, transfer_sma < 5.0 AS below_jupiterFROM lambert_transfer(3, 4, '2026-05-01'::timestamptz, '2027-01-15'::timestamptz);