Skip to content

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.

Both tools implement the Izzo (2015) Lambert solver. The same algorithm, different execution environments.

from astropy import units as u
from astropy.time import Time
from poliastro.bodies import Earth, Mars, Sun
from poliastro.ephem import Ephem
from poliastro.iod import izzo
from poliastro.util import norm
# Get planet positions from built-in ephemeris
dep_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 vector
r_mars = mars_ephem.rv(arr_time)[0] # position vector
tof = arr_time - dep_time # time of flight
# Solve Lambert problem
(v_dep, v_arr), = izzo.lambert(Sun.k, r_earth, r_mars, tof)
# Compute C3
v_earth = earth_ephem.rv(dep_time)[1]
v_inf = v_dep - v_earth
c3 = 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.

The parameter sweep that reveals launch windows. This is where the architectural difference matters most.

from astropy import units as u
from astropy.time import Time
import numpy as np
from poliastro.bodies import Earth, Mars, Sun
from poliastro.ephem import Ephem
from poliastro.iod import izzo
from 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 matplotlib
import matplotlib.pyplot as plt
plt.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.

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 u
from astropy.time import Time
from poliastro.bodies import Sun
from poliastro.twobody import Orbit
import numpy as np
# Define a comet orbit from classical elements
comet = 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 days
future = comet.propagate(90 * u.day)
r = future.r.to(u.AU)
print(f"Position: {r}")
# Time series
times = np.linspace(0, 365, 100) * u.day
positions = [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.

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.

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.

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.

from poliastro.twobody.propagation import CowellPropagator
from 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.

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”
-- Observe 12,000 satellites in 17ms
SELECT s.name,
topo_elevation(observe(s.tle, '40.0N 105.3W 1655m'::observer, now())) AS el
FROM satellites s
WHERE 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.

-- Fit an orbit from observations
SELECT iterations, round(rms_final::numeric, 6) AS rms, status, fitted_tle
FROM 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.

-- Combine Lambert transfer analysis with mission constraint database
SELECT 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' END
FROM transfer_survey t
JOIN mission_constraints m ON m.target = t.target
WHERE 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.

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.

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.

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.

The two tools complement each other naturally:

  1. Explore with Poliastro. Use a Jupyter notebook to understand the problem — plot orbits, visualize transfer geometry, prototype maneuver sequences. This is the design phase.

  2. 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.

  3. Correlate with pg_orrery. JOIN transfer results with launch vehicle constraints, budget timelines, or historical mission data. Filter to feasible solutions.

  4. 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.

  5. 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.