From Poliastro to SQL
Poliastro is a Python library for interactive astrodynamics and orbital mechanics. Built on Astropy, it provides Keplerian propagation, Lambert transfers, orbit plotting, and maneuver analysis with a clean Pythonic API. If you’re doing orbital mechanics in Jupyter notebooks, you’ve probably used it.
pg_orrery overlaps with Poliastro on two core computations — Lambert transfers and Keplerian propagation — and extends beyond it with SGP4 satellite tracking, orbit determination, and GiST-indexed catalog operations. This page compares the workflows where both tools operate, and identifies where each is the better fit.
Lambert transfers
Section titled “Lambert transfers”Both tools implement the Izzo (2015) Lambert solver. The same algorithm, different execution environments.
from astropy import units as ufrom astropy.time import Timefrom poliastro.bodies import Earth, Mars, Sunfrom poliastro.ephem import Ephemfrom poliastro.iod import izzofrom poliastro.util import norm
# Get planet positions from built-in ephemerisdep_time = Time("2028-10-01", scale="tdb")arr_time = Time("2029-06-15", scale="tdb")
earth_ephem = Ephem.from_body(Earth, dep_time)mars_ephem = Ephem.from_body(Mars, arr_time)
r_earth = earth_ephem.rv(dep_time)[0] # position vectorr_mars = mars_ephem.rv(arr_time)[0] # position vectortof = arr_time - dep_time # time of flight
# Solve Lambert problem(v_dep, v_arr), = izzo.lambert(Sun.k, r_earth, r_mars, tof)
# Compute C3v_earth = earth_ephem.rv(dep_time)[1]v_inf = v_dep - v_earthc3 = norm(v_inf).to(u.km / u.s) ** 2
print(f"Departure C3: {c3:.2f}")Several objects are involved: Ephem for planet positions, Time for epochs, Astropy units for dimensional analysis, and the izzo module for the actual solve. Each intermediate result has attached units. This is excellent for interactive exploration in a notebook — you can inspect each step, change units, and see what you’re working with.
For a single transfer, this is perfectly fine. The friction appears when you need many transfers.
SELECT round(c3_departure::numeric, 2) AS c3_km2s2, round(c3_arrival::numeric, 2) AS c3_arrive, round(v_inf_departure::numeric, 3) AS v_inf_dep_kms, round(tof_days::numeric, 1) AS flight_days, round(transfer_sma::numeric, 4) AS sma_auFROM lambert_transfer(3, 4, '2028-10-01'::timestamptz, '2029-06-15'::timestamptz);One function call. Planet positions, Lambert solve, and C3 computation happen internally. The body IDs (3=Earth, 4=Mars) and timestamps are the only inputs. No unit objects, no intermediate vectors, no ephemeris loading.
Pork chop plots
Section titled “Pork chop plots”The parameter sweep that reveals launch windows. This is where the architectural difference matters most.
from astropy import units as ufrom astropy.time import Timeimport numpy as npfrom poliastro.bodies import Earth, Mars, Sunfrom poliastro.ephem import Ephemfrom poliastro.iod import izzofrom poliastro.util import norm
dep_dates = Time(np.arange( Time("2028-08-01").jd, Time("2029-01-01").jd, 1.0 # 1-day steps), format="jd")
arr_dates = Time(np.arange( Time("2029-03-01").jd, Time("2029-10-01").jd, 1.0), format="jd")
c3_grid = np.full((len(dep_dates), len(arr_dates)), np.nan)
for i, dep in enumerate(dep_dates): earth = Ephem.from_body(Earth, dep) r_earth, v_earth = earth.rv(dep)
for j, arr in enumerate(arr_dates): mars = Ephem.from_body(Mars, arr) r_mars = mars.rv(arr)[0] tof = arr - dep
if tof.to(u.day).value < 90: continue
try: (v_dep, _), = izzo.lambert(Sun.k, r_earth, r_mars, tof) v_inf = v_dep - v_earth c3_grid[i, j] = norm(v_inf).to(u.km / u.s).value ** 2 except Exception: pass
# Plot with matplotlibimport matplotlib.pyplot as pltplt.contourf(arr_dates.datetime, dep_dates.datetime, c3_grid, levels=20)plt.colorbar(label="C3 (km²/s²)")plt.xlabel("Arrival"); plt.ylabel("Departure")plt.show()The nested loop iterates over ~23,000 date combinations. Each iteration constructs ephemeris objects, solves Lambert, handles exceptions, and stores the result. With Poliastro’s overhead per iteration (unit conversions, object construction), a dense grid can take minutes.
The plotting integration is the payoff — matplotlib is right there, and the result renders inline in a Jupyter notebook.
-- Same grid: 153 departure x 214 arrival = ~32,700 transfersSELECT dep::date AS departure, arr::date AS arrival, round(c3_departure::numeric, 2) AS c3_km2s2, round(tof_days::numeric, 0) AS flight_daysFROM generate_series( '2028-08-01'::timestamptz, '2029-01-01'::timestamptz, interval '1 day') AS dep, generate_series( '2029-03-01'::timestamptz, '2029-10-01'::timestamptz, interval '1 day') AS arr,LATERAL lambert_transfer(3, 4, dep, arr) AS xferWHERE tof_days > 90 AND c3_departure < 50;~32,700 Lambert solves. Runs in seconds with automatic parallelism across cores. Export to CSV for plotting:
COPY ( SELECT dep::date, arr::date, round(c3_departure::numeric, 2) AS c3 FROM generate_series( '2028-08-01'::timestamptz, '2029-01-01'::timestamptz, interval '1 day') AS dep, generate_series( '2029-03-01'::timestamptz, '2029-10-01'::timestamptz, interval '1 day') AS arr, LATERAL lambert_transfer(3, 4, dep, arr) AS xfer WHERE tof_days > 90) TO '/tmp/porkchop.csv' WITH CSV HEADER;Then plot with gnuplot, matplotlib, or any contour tool. The computation and visualization are decoupled — pg_orrery produces data, you render it however you prefer.
Keplerian propagation
Section titled “Keplerian propagation”Propagating an orbit forward in time using two-body mechanics. This is how you track comets and asteroids from their osculating orbital elements.
from astropy import units as ufrom astropy.time import Timefrom poliastro.bodies import Sunfrom poliastro.twobody import Orbitimport numpy as np
# Define a comet orbit from classical elementscomet = Orbit.from_classical( Sun, a=2.7 * u.AU, ecc=0.85 * u.one, inc=12.0 * u.deg, raan=65.0 * u.deg, argp=180.0 * u.deg, nu=0.0 * u.deg, epoch=Time("2025-01-15"))
# Propagate forward 90 daysfuture = comet.propagate(90 * u.day)r = future.r.to(u.AU)print(f"Position: {r}")
# Time seriestimes = np.linspace(0, 365, 100) * u.daypositions = [comet.propagate(t).r.to(u.AU).value for t in times]Poliastro’s Orbit object handles the Kepler equation internally. Propagation returns a new Orbit with updated elements. The unit-annotated result is self-documenting.
The time series loop is explicit — each propagation creates a new object.
-- Propagate a comet orbit and observe from EarthSELECT t, topo_azimuth(obs) AS az, topo_elevation(obs) AS el, topo_range(obs) / 149597870.7 AS range_auFROM generate_series( '2025-01-15'::timestamptz, '2026-01-15'::timestamptz, interval '1 day' ) AS t,LATERAL comet_observe( 2.7, -- semi-major axis (AU) 0.85, -- eccentricity 12.0, -- inclination (deg) 65.0, -- RAAN (deg) 180.0, -- argument of perihelion (deg) 0.0, -- mean anomaly (deg) '2025-01-15'::timestamptz, -- epoch '40.0N 105.3W 1655m'::observer, t) AS obs;comet_observe() wraps Keplerian propagation with the topocentric observation pipeline. The time series is generate_series — one row per timestep, parallel-safe, no loop management.
For raw heliocentric position without the observation step:
SELECT t, helio_x(kepler_propagate(2.7, 0.85, 12.0, 65.0, 180.0, 0.0, '2025-01-15'::timestamptz, t)) AS x_au, helio_y(kepler_propagate(2.7, 0.85, 12.0, 65.0, 180.0, 0.0, '2025-01-15'::timestamptz, t)) AS y_au, helio_z(kepler_propagate(2.7, 0.85, 12.0, 65.0, 180.0, 0.0, '2025-01-15'::timestamptz, t)) AS z_auFROM generate_series( '2025-01-15'::timestamptz, '2026-01-15'::timestamptz, interval '1 day') AS t;What Poliastro does that pg_orrery doesn’t
Section titled “What Poliastro does that pg_orrery doesn’t”Poliastro is a broader orbital mechanics toolkit. Several of its capabilities have no pg_orrery equivalent.
Orbit plotting
Section titled “Orbit plotting”from poliastro.plotting import OrbitPlotter3D
plotter = OrbitPlotter3D()plotter.plot(earth_orbit, label="Earth")plotter.plot(mars_orbit, label="Mars")plotter.plot(transfer, label="Transfer")plotter.show()Interactive 3D orbit visualization in Jupyter notebooks. pg_orrery returns coordinates — you build your own visualization externally.
Maneuver planning
Section titled “Maneuver planning”from poliastro.maneuver import Maneuver
hohmann = Maneuver.hohmann(initial_orbit, target_radius)bielliptic = Maneuver.bielliptic(initial_orbit, r_b, target_radius)
print(f"Hohmann delta-v: {hohmann.get_total_cost()}")print(f"Bielliptic delta-v: {bielliptic.get_total_cost()}")Poliastro computes delta-v budgets for standard maneuvers. pg_orrery’s Lambert solver gives you the transfer orbit but doesn’t model the departure/arrival maneuvers.
Perturbation models
Section titled “Perturbation models”from poliastro.twobody.propagation import CowellPropagatorfrom poliastro.twobody.perturbations import J2_perturbation
orbit_with_j2 = orbit.propagate( 30 * u.day, method=CowellPropagator(f=J2_perturbation))Poliastro supports numerical integration with custom perturbation functions — J2 oblateness, atmospheric drag, third-body gravity, solar radiation pressure. pg_orrery’s Keplerian propagation is strictly two-body.
Astropy integration
Section titled “Astropy integration”Poliastro builds on Astropy’s unit system, time handling, and coordinate frames. Results carry physical units and can be converted freely. pg_orrery returns bare doubles — you know the units from the documentation, but the database doesn’t enforce them.
What pg_orrery does that Poliastro doesn’t
Section titled “What pg_orrery does that Poliastro doesn’t”SGP4/SDP4 satellite tracking
Section titled “SGP4/SDP4 satellite tracking”-- Observe 12,000 satellites in 17msSELECT s.name, topo_elevation(observe(s.tle, '40.0N 105.3W 1655m'::observer, now())) AS elFROM satellites sWHERE topo_elevation(observe(s.tle, '40.0N 105.3W 1655m'::observer, now())) > 0;Poliastro has no TLE support, no SGP4, no satellite catalog operations. The entire satellite tracking domain — pass prediction, conjunction screening, GiST indexing — is absent.
Orbit determination
Section titled “Orbit determination”-- Fit an orbit from observationsSELECT iterations, round(rms_final::numeric, 6) AS rms, status, fitted_tleFROM tle_from_angles( ra_hours := obs_ra, dec_degrees := obs_dec, times := obs_times, obs := station);Poliastro does not fit orbits from observations. pg_orrery’s differential correction solver handles ECI, topocentric, and angles-only OD with Gauss/Gibbs IOD bootstrap.
Data correlation via SQL
Section titled “Data correlation via SQL”-- Combine Lambert transfer analysis with mission constraint databaseSELECT t.target, t.dep::date, t.c3, m.max_c3_budget, m.launch_vehicle, CASE WHEN t.c3 < m.max_c3_budget THEN 'FEASIBLE' ELSE 'OVER BUDGET' ENDFROM transfer_survey tJOIN mission_constraints m ON m.target = t.targetWHERE t.c3 < m.max_c3_budget * 1.1;Poliastro results live in Python memory. Correlating with mission databases, launch vehicle catalogs, or historical data requires loading everything into Python. pg_orrery results are database rows — correlation is a JOIN.
Automatic parallelism
Section titled “Automatic parallelism”All pg_orrery functions are PARALLEL SAFE. PostgreSQL distributes work across CPU cores automatically. Poliastro runs single-threaded by default — parallelism requires explicit multiprocessing or joblib setup.
Where Poliastro wins
Section titled “Where Poliastro wins”Interactive exploration. Poliastro in a Jupyter notebook is the best way to explore orbital mechanics interactively. Plot orbits, change parameters, see results immediately. pg_orrery returns tabular data — useful, but not visual.
Unit safety. Astropy units catch dimension errors at runtime. Passing kilometers where AU are expected raises an exception. pg_orrery uses bare doubles — unit errors are silent.
Perturbation modeling. Poliastro’s numerical integrators with custom force models handle real-world orbit analysis that two-body Keplerian propagation cannot.
Maneuver analysis. Delta-v budgets, Hohmann transfers, bielliptic maneuvers — Poliastro models the spacecraft operations side. pg_orrery provides the trajectory geometry.
Orbit visualization. 2D and 3D orbit plots, ground tracks on map projections, interactive widgets in notebooks. pg_orrery produces numbers for external rendering.
Broader ephemeris support. Poliastro accesses any Astropy-compatible ephemeris source, including custom SPK kernels for spacecraft and small bodies.
Where pg_orrery wins
Section titled “Where pg_orrery wins”Throughput. 22,500 Lambert solves in 8.3 seconds vs. minutes in Poliastro for the same grid. The difference is compiled C + parallel execution vs. Python object construction per iteration.
Satellite operations. SGP4, TLEs, pass prediction, conjunction screening, orbit determination — Poliastro has none of these. If you work with artificial satellites, pg_orrery covers the domain.
Data integration. Results live in PostgreSQL alongside your other data. JOIN with mission databases, observation logs, or any other table.
Reproducibility. A SQL query is a complete specification. No virtual environment, no package versions, no imports. Share the query and it runs identically on any pg_orrery installation.
Deployment simplicity. pg_orrery is a PostgreSQL extension — install once, available to every client. Poliastro requires a Python environment with Astropy, NumPy, SciPy, and their transitive dependencies.
A combined workflow
Section titled “A combined workflow”The two tools complement each other naturally:
-
Explore with Poliastro. Use a Jupyter notebook to understand the problem — plot orbits, visualize transfer geometry, prototype maneuver sequences. This is the design phase.
-
Survey with pg_orrery. Once you know what you’re looking for, run the parameter sweep in SQL. 100,000 Lambert solves to identify the best launch windows. Store the results in a table.
-
Correlate with pg_orrery. JOIN transfer results with launch vehicle constraints, budget timelines, or historical mission data. Filter to feasible solutions.
-
Refine with Poliastro. Take the best candidates back to Python for detailed analysis — add perturbations, model the departure spiral, compute the precise delta-v budget.
-
Operate with pg_orrery. Once you have TLEs (from catalog data or from pg_orrery’s OD solver), the operational tracking — observation, pass prediction, conjunction screening — stays in SQL.