Update docs for v0.8.0 orbital_elements type and MPC parser

Add orbital_elements (72-byte Keplerian element type) to types reference,
three new function sections (oe_from_mpc, small_body_heliocentric,
small_body_observe) to the functions reference, MPC catalog loading
workflow to the comets/asteroids guide, and update CLAUDE.md with
v0.8.0 version numbers, 82 functions, 8 types, 16 test suites.
This commit is contained in:
Ryan Malloy 2026-02-18 14:22:39 -07:00
parent 1adab6e136
commit 53733daeba
5 changed files with 352 additions and 21 deletions

View File

@ -1,9 +1,9 @@
# pg_orrery — A Database Orrery for PostgreSQL
## What This Is
A database orrery — celestial mechanics types and functions for PostgreSQL. Native C extension using PGXS, 68 SQL functions, 7 custom types, covering satellites (SGP4/SDP4), planets (VSOP87 + optional JPL DE441), Moon (ELP2000-82B), 19 planetary moons (L1.2/TASS17/GUST86/MarsSat), stars, comets, Jupiter radio bursts, and interplanetary Lambert transfers.
A database orrery — celestial mechanics types and functions for PostgreSQL. Native C extension using PGXS, 82 SQL functions, 8 custom types, covering satellites (SGP4/SDP4), planets (VSOP87 + optional JPL DE441), Moon (ELP2000-82B), 19 planetary moons (L1.2/TASS17/GUST86/MarsSat), stars, comets, asteroids (MPC catalog), Jupiter radio bursts, and interplanetary Lambert transfers.
**Current version:** 0.3.0 on branch `phase/solar-system-expansion`
**Current version:** 0.8.0 on branch `phase/spgist-orbital-trie`
**Repository:** https://git.supported.systems/warehack.ing/pg_orrery
**Documentation:** https://pg-orrery.warehack.ing
@ -11,7 +11,7 @@ A database orrery — celestial mechanics types and functions for PostgreSQL. Na
```bash
make PG_CONFIG=/usr/bin/pg_config # Compile with PGXS
sudo make install PG_CONFIG=/usr/bin/pg_config # Install extension
make installcheck PG_CONFIG=/usr/bin/pg_config # Run 13 regression test suites
make installcheck PG_CONFIG=/usr/bin/pg_config # Run 16 regression test suites
```
Requires: PostgreSQL 17 development headers, GCC, Make.
@ -27,14 +27,24 @@ Image: `git.supported.systems/warehack.ing/pg_orrery:pg17`
## Project Layout
```
pg_orrery.control # Extension metadata (version 0.3.0)
pg_orrery.control # Extension metadata (version 0.8.0)
Makefile # PGXS build + Docker targets
sql/
pg_orrery--0.1.0.sql # v0.1.0: satellite types/functions/operators
pg_orrery--0.2.0.sql # v0.2.0: solar system (57 functions)
pg_orrery--0.3.0.sql # v0.3.0: complete extension (68 functions)
pg_orrery--0.3.0.sql # v0.3.0: DE ephemeris (68 functions)
pg_orrery--0.4.0.sql # v0.4.0: orbit determination
pg_orrery--0.5.0.sql # v0.5.0: SP-GiST orbital trie
pg_orrery--0.6.0.sql # v0.6.0: conjunction screening
pg_orrery--0.7.0.sql # v0.7.0: GiST improvements
pg_orrery--0.8.0.sql # v0.8.0: orbital_elements type + MPC parser (82 functions)
pg_orrery--0.1.0--0.2.0.sql # Migration: v0.1.0 → v0.2.0 (adds solar system)
pg_orrery--0.2.0--0.3.0.sql # Migration: v0.2.0 → v0.3.0 (adds DE ephemeris)
pg_orrery--0.3.0--0.4.0.sql # Migration: v0.3.0 → v0.4.0
pg_orrery--0.4.0--0.5.0.sql # Migration: v0.4.0 → v0.5.0
pg_orrery--0.5.0--0.6.0.sql # Migration: v0.5.0 → v0.6.0
pg_orrery--0.6.0--0.7.0.sql # Migration: v0.6.0 → v0.7.0
pg_orrery--0.7.0--0.8.0.sql # Migration: v0.7.0 → v0.8.0 (orbital_elements type)
src/
pg_orrery.c # PG_MODULE_MAGIC + _PG_init() (GUC registration)
types.h # All struct definitions + constants + DE body ID mapping
@ -56,6 +66,8 @@ src/
planet_funcs.c # planet_observe(), planet_heliocentric(), sun/moon_observe()
star_funcs.c # star_observe(), star_observe_safe()
kepler_funcs.c # kepler_propagate(), comet_observe()
kepler.h # Shared Kepler solver interface (kepler_position())
orbital_elements_type.c # orbital_elements type, MPC parser, small_body_observe()
l12.c / l12.h # L1.2 Galilean moon theory (Lieske 1998)
tass17.c / tass17.h # TASS 1.7 Saturn moon theory (Vienne & Duriez 1995)
gust86.c / gust86.h # GUST86 Uranus moon theory (Laskar & Jacobson 1987)
@ -80,7 +92,7 @@ src/
PROVENANCE.md # Vendoring decision, modifications, verification
LICENSE # MIT license (Bill Gray / Project Pluto)
test/
sql/ # 13 regression test suites
sql/ # 16 regression test suites
expected/ # Expected output
data/vallado_518.json # 518 Vallado test vectors (AIAA 2006-6753-Rev1)
docs/
@ -104,8 +116,9 @@ All types are fixed-size, `STORAGE = plain`, `ALIGNMENT = double`. No TOAST over
| `observer` | 24 | lat, lon (radians), alt_m (meters) |
| `pass_event` | 48 | AOS/MAX/LOS times + max_el + AOS/LOS azimuth |
| `heliocentric` | 24 | x, y, z in AU (ecliptic J2000 frame) |
| `orbital_elements` | 72 | Classical Keplerian elements for comets/asteroids (epoch, q, e, inc, omega, Omega, tp, H, G) |
## Function Domains (68 total)
## Function Domains (82 total)
| Domain | Theory | Key Functions | Count |
|--------|--------|---------------|-------|
@ -114,7 +127,7 @@ All types are fixed-size, `STORAGE = plain`, `ALIGNMENT = double`. No TOAST over
| Sun/Moon | VSOP87 + ELP2000-82B | `sun_observe()`, `moon_observe()` | 2 |
| Planetary moons | L1.2, TASS17, GUST86, MarsSat | `galilean_observe()`, `saturn_moon_observe()` | 4 |
| Stars | J2000 + IAU 1976 precession | `star_observe()`, `star_observe_safe()` | 2 |
| Comets/asteroids | Two-body Keplerian | `kepler_propagate()`, `comet_observe()` | 2 |
| Comets/asteroids | Two-body Keplerian + MPC | `small_body_observe()`, `oe_from_mpc()`, `kepler_propagate()` | 16 |
| Jupiter radio | Carr et al. (1983) | `jupiter_burst_probability()` | 3 |
| Transfers | Lambert (Izzo 2015) | `lambert_transfer()`, `lambert_c3()` | 2 |
| DE ephemeris | JPL DE440/441 (optional) | `planet_observe_de()`, `moon_observe_de()` | 11 |
@ -239,7 +252,7 @@ All numerical logic is byte-identical to upstream. Verified against 518 Vallado
## Testing
13 regression test suites via `make installcheck`:
16 regression test suites via `make installcheck`:
| Suite | What it tests |
|-------|--------------|
@ -256,10 +269,13 @@ All numerical logic is byte-identical to upstream. Verified against 518 Vallado
| lambert_transfer | Lambert solver, lambert_c3, pork chop grid, error handling |
| de_ephemeris | DE function fallback to VSOP87, cross-provider consistency, error handling |
| vallado_518 | 518 Vallado test vectors (AIAA 2006-6753-Rev1), per-satellite breakdown |
| od_fit | Orbit determination from ECI/topocentric/angles-only observations |
| orbital_elements | orbital_elements type I/O, MPC parser, small_body_observe/heliocentric |
| spgist_tle | SP-GiST orbital trie index operations |
### PG Version Matrix
Test all 13 regression suites + DE reader unit test across PostgreSQL 14-18 using Docker:
Test all 16 regression suites + DE reader unit test across PostgreSQL 14-18 using Docker:
```bash
make test-matrix # Full matrix (PG 14-18)
@ -283,9 +299,9 @@ Logs saved to `test/matrix-logs/pg${ver}.log`. The script reuses the Dockerfile
**Live:** https://pg-orrery.warehack.ing
Starlight docs at `docs/`36 MDX pages covering all domains.
Starlight docs at `docs/`42 MDX pages covering all domains.
Sections: Getting Started, Guides (9 domain walkthroughs incl. DE ephemeris), Workflow Translation (Skyfield/Horizons/GMAT/Radio Jupiter Pro comparisons), Reference (all 68 functions incl. DE variants), Architecture (Hamilton's principles, constant custody, observation pipeline), Performance (benchmarks).
Sections: Getting Started, Guides (9 domain walkthroughs incl. DE ephemeris), Workflow Translation (Skyfield/Horizons/GMAT/Radio Jupiter Pro comparisons), Reference (all 82 functions incl. DE variants + orbital_elements), Architecture (Hamilton's principles, constant custody, observation pipeline), Performance (benchmarks).
### Local Development
```bash
@ -301,7 +317,7 @@ The docs site deploys to the `warehack.ing` VPS (`149.28.126.25`) which runs cad
```bash
ssh -A warehack-ing@pg-orrery.warehack.ing
cd ~/pg_orrery
git pull origin phase/solar-system-expansion # or the current branch
git pull origin phase/spgist-orbital-trie # or the current branch
cd docs
make prod # builds image + starts container
```
@ -310,7 +326,7 @@ make prod # builds image + starts containe
```bash
ssh -A warehack-ing@pg-orrery.warehack.ing
git clone git@git.supported.systems:warehack.ing/pg_orrery.git
cd pg_orrery && git checkout phase/solar-system-expansion
cd pg_orrery && git checkout phase/spgist-orbital-trie
cat > docs/.env << 'EOF'
COMPOSE_PROJECT_NAME=pg-orrery-docs
NODE_ENV=production
@ -343,6 +359,6 @@ cd docs && make prod
## Git Conventions
- One commit per logical change
- Branch per phase: `phase/solar-system-expansion`
- Tag releases: `v0.1.0`, `v0.2.0`
- Branch per phase: `phase/spgist-orbital-trie`
- Tag releases: `v0.1.0`, `v0.2.0`, `v0.3.0`
- Commit messages: imperative mood, no AI attribution

View File

@ -22,7 +22,7 @@ PostGIS added spatial awareness to PostgreSQL — suddenly your database underst
| Moon | ELP2000-82B (Chapront, 1988) | `moon_observe()` | ~10 arcseconds |
| Planetary moons | L1.2, TASS17, GUST86, MarsSat | `galilean_observe()`, etc. | ~1-10 arcseconds |
| Stars | J2000 catalog + precession | `star_observe()` | Limited by catalog |
| Comets/asteroids | Two-body Keplerian | `kepler_propagate()`, `comet_observe()` | Varies with eccentricity |
| Comets/asteroids | Two-body Keplerian | `small_body_observe()`, `oe_from_mpc()`, `kepler_propagate()` | Varies with eccentricity |
| Jupiter radio | Carr et al. (1983) sources | `jupiter_burst_probability()` | Empirical probability |
| Transfers | Lambert (Izzo, 2015) | `lambert_transfer()`, `lambert_c3()` | Ballistic two-body |
| DE ephemeris (optional) | JPL DE440/441 | `planet_observe_de()`, `moon_observe_de()` | ~0.1 milliarcsecond |

View File

@ -21,17 +21,22 @@ The pattern is familiar: download elements, propagate in Python or C, transform
## What changes with pg_orrery
Two functions handle comet/asteroid computation:
Five functions handle comet/asteroid computation:
| Function | What it does |
|---|---|
| `kepler_propagate(q, e, i, omega, Omega, T, time)` | Propagates orbital elements to a heliocentric position (AU) |
| `comet_observe(q, e, i, omega, Omega, T, ex, ey, ez, observer, time)` | Full observation pipeline: propagate + geocentric transform + topocentric |
| `oe_from_mpc(line)` | Parses one MPCORB.DAT line into an `orbital_elements` type |
| `small_body_heliocentric(oe, time)` | Heliocentric position from bundled elements |
| `small_body_observe(oe, observer, time)` | Topocentric observation — auto-fetches Earth via VSOP87 |
`kepler_propagate()` solves Kepler's equation for elliptic (e < 1), parabolic (e = 1), and hyperbolic (e > 1) orbits. The solver handles all three cases with appropriate numerical methods.
`comet_observe()` wraps the full chain: propagate the comet's position, subtract Earth's heliocentric position, and transform to horizon coordinates. You supply Earth's position as three floats (ecliptic J2000, AU) because you might want to compute it once and reuse it across many comets.
`small_body_observe()` (v0.8.0) does the same thing but fetches Earth's position automatically — you just pass the `orbital_elements` type and an observer. See the [orbital_elements type section](#the-orbital_elements-type) below.
The parameters map directly to MPC orbital element format:
| Parameter | MPC field | Units |
@ -56,6 +61,107 @@ Keplerian propagation assumes the body is influenced only by the Sun. Real small
For MPC elements less than a few months old, two-body propagation is typically accurate to a few arcminutes for asteroids and tens of arcminutes for comets. Fresh elements give better results.
## The `orbital_elements` type
The raw-parameter functions (`kepler_propagate`, `comet_observe`) work well when you have elements in variables or a table with individual columns. But they require passing 611 float8 arguments per call, and `comet_observe` requires you to manually fetch Earth's position.
The `orbital_elements` type (v0.8.0) bundles all nine classical elements into a single 72-byte PostgreSQL datum:
```sql
-- Construct from a literal
SELECT '(2460605.5,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements;
-- Or parse directly from the MPC catalog
SELECT oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
);
```
With bundled elements, observation becomes a single function call:
```sql
-- Before (comet_observe): 11 arguments, manual Earth fetch
WITH earth AS (SELECT planet_heliocentric(3, now()) AS h)
SELECT topo_elevation(comet_observe(
2.5478, 0.0789, 10.59, 73.43, 80.27, 2460319.0,
helio_x(h), helio_y(h), helio_z(h),
'40.0N 105.3W 1655m'::observer, now()))
FROM earth;
-- After (small_body_observe): 3 arguments, Earth auto-fetched
SELECT topo_elevation(small_body_observe(
oe, '40.0N 105.3W 1655m'::observer, now()))
FROM asteroid_catalog;
```
### Load and query the MPC catalog
The MPC publishes MPCORB.DAT — orbital elements for every numbered asteroid. Here's how to load it into PostgreSQL:
<Steps>
1. **Create a table with an `orbital_elements` column:**
```sql
CREATE TABLE asteroids (
number int PRIMARY KEY,
name text,
oe orbital_elements NOT NULL
);
```
2. **Load via a staging table:**
```sql
-- Stage the raw text lines
CREATE TEMP TABLE mpc_raw (line text);
\copy mpc_raw FROM 'MPCORB.DAT'
-- Parse into orbital_elements, extract number and name
INSERT INTO asteroids (number, name, oe)
SELECT substring(line FROM 1 FOR 7)::int,
trim(substring(line FROM 167 FOR 30)),
oe_from_mpc(line)
FROM mpc_raw
WHERE length(line) >= 103
AND substring(line FROM 1 FOR 7) ~ '^\s*\d+$';
DROP TABLE mpc_raw;
```
3. **Query: what asteroids are above 20 degrees tonight?**
```sql
SELECT name, number,
round(topo_elevation(t)::numeric, 1) AS el,
round(topo_azimuth(t)::numeric, 1) AS az,
round((topo_range(t) / 149597870.7)::numeric, 2) AS dist_au
FROM asteroids,
small_body_observe(oe, '40.0N 105.3W 1655m'::observer, now()) AS t
WHERE topo_elevation(t) > 20
ORDER BY topo_elevation(t) DESC
LIMIT 20;
```
4. **Query: heliocentric distance of Ceres over 6 months:**
```sql
SELECT t::date AS date,
round(helio_distance(
small_body_heliocentric(oe, t))::numeric, 4) AS dist_au
FROM asteroids,
generate_series(
'2025-01-01'::timestamptz,
'2025-07-01'::timestamptz,
interval '15 days'
) AS t
WHERE number = 1;
```
</Steps>
<Aside type="tip">
For batch observation at a single time, `comet_observe()` is still more efficient — it lets you compute Earth's VSOP87 position once with `planet_heliocentric(3, t)` and reuse it across all objects. `small_body_observe()` re-fetches Earth on every call. For interactive single-object queries, `small_body_observe()` is simpler.
</Aside>
## Try it
### Circular orbit sanity check

View File

@ -1,12 +1,12 @@
---
title: "Functions: Stars & Comets"
title: "Functions: Stars, Comets & Asteroids"
sidebar:
order: 5
---
import { Aside, Tabs, TabItem } from "@astrojs/starlight/components";
Functions for computing topocentric positions of stars from catalog coordinates, propagating comets and asteroids on Keplerian orbits, and observing them from Earth.
Functions for computing topocentric positions of stars from catalog coordinates, propagating comets and asteroids on Keplerian orbits, and observing them from Earth. The `orbital_elements` type (v0.8.0) bundles Keplerian elements into a first-class PostgreSQL datum, with `oe_from_mpc()` for bulk-loading the MPC catalog and `small_body_observe()` for ergonomic topocentric observation.
---
@ -255,3 +255,144 @@ FROM comet_catalog, earth,
WHERE topo_elevation(c) > 0
ORDER BY topo_range(c);
```
---
## oe_from_mpc
Parses one line of the MPC MPCORB.DAT fixed-width format into an `orbital_elements` type. The MPC publishes orbital elements for over 1.3 million numbered asteroids in this format.
### Signature
```sql
oe_from_mpc(line text) → orbital_elements
```
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `line` | `text` | One complete line from MPCORB.DAT (at least 103 characters) |
### Returns
An `orbital_elements` with all nine fields populated. The parser performs several conversions at parse time:
- **Packed epoch** (e.g. `K24AM`) is decoded to a Julian date. The century letter (`I`=1800, `J`=1900, `K`=2000), two-digit year, packed month (`1-9`, `A-C`), and packed day (`1-9`, `A-V`) are expanded to a calendar date.
- **Perihelion distance** is derived from the MPC's semi-major axis and eccentricity: q = a × (1 e).
- **Perihelion time** is computed from the epoch and mean anomaly via Gauss's constant: tp = epoch M / n, where n = k / a^(3/2).
<Aside type="tip">
The full MPCORB.DAT format is documented at the [IAU Minor Planet Center](https://www.minorplanetcenter.net/iau/info/MPOrbitFormat.html). The file is freely downloadable (~280 MB compressed) and contains orbital elements for all numbered and multi-opposition asteroids.
</Aside>
### Example
```sql
-- Parse Ceres, extract semi-major axis and period
WITH ceres AS (
SELECT oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
) AS oe
)
SELECT round(oe_semi_major_axis(oe)::numeric, 4) AS a_au,
round(oe_period_years(oe)::numeric, 2) AS period_yr,
round(oe_inclination(oe)::numeric, 3) AS inc_deg,
round(oe_h_mag(oe)::numeric, 2) AS h_mag
FROM ceres;
```
---
## small_body_heliocentric
Propagates an `orbital_elements` to a heliocentric ecliptic J2000 position at a given time using two-body Keplerian mechanics. Extracts q, e, inc, omega, Omega, and tp from the type and calls the internal Kepler solver.
### Signature
```sql
small_body_heliocentric(oe orbital_elements, t timestamptz) → heliocentric
```
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `oe` | `orbital_elements` | Bundled orbital elements |
| `t` | `timestamptz` | Evaluation time |
### Returns
A `heliocentric` position in AU (ecliptic J2000 frame). Identical to calling `kepler_propagate()` with the individual fields extracted from the type.
### Example
```sql
-- Propagate Ceres to 2025-01-01, check heliocentric distance
WITH ceres AS (
SELECT oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
) AS oe
)
SELECT round(helio_x(h)::numeric, 4) AS x_au,
round(helio_y(h)::numeric, 4) AS y_au,
round(helio_z(h)::numeric, 4) AS z_au,
round(helio_distance(h)::numeric, 3) AS dist_au
FROM ceres,
small_body_heliocentric(oe, '2025-01-01 00:00:00+00') AS h;
```
---
## small_body_observe
Computes the topocentric position of a comet or asteroid from its `orbital_elements` as seen by an Earth-based observer. Auto-fetches Earth's heliocentric position via VSOP87, matching the ergonomics of `planet_observe()`.
### Signature
```sql
small_body_observe(oe orbital_elements, obs observer, t timestamptz) → topocentric
```
### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `oe` | `orbital_elements` | Bundled orbital elements |
| `obs` | `observer` | Observer location on Earth |
| `t` | `timestamptz` | Observation time |
### Returns
A `topocentric` with azimuth, elevation (degrees), range (km), and range rate (km/s).
<Aside type="note">
Unlike `comet_observe()` which requires you to pass Earth's position as three separate floats, `small_body_observe()` fetches Earth internally via VSOP87. This is simpler for single-object queries. For batch observations at the same time, `comet_observe()` still lets you compute Earth's position once and reuse it.
</Aside>
### Example
```sql
-- Observe Ceres from Boulder
WITH ceres AS (
SELECT oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
) AS oe
)
SELECT round(topo_azimuth(t)::numeric, 1) AS az,
round(topo_elevation(t)::numeric, 1) AS el,
round((topo_range(t) / 149597870.7)::numeric, 3) AS dist_au
FROM ceres,
small_body_observe(oe, '40.0N 105.3W 1655m'::observer, now()) AS t;
```
```sql
-- Which asteroids in the catalog are above 20 degrees tonight?
SELECT name,
round(topo_elevation(t)::numeric, 1) AS el,
round(topo_azimuth(t)::numeric, 1) AS az
FROM asteroid_catalog,
small_body_observe(oe, '40.0N 105.3W 1655m'::observer, now()) AS t
WHERE topo_elevation(t) > 20
ORDER BY topo_elevation(t) DESC;
```

View File

@ -6,7 +6,7 @@ sidebar:
import { Aside, Tabs, TabItem } from "@astrojs/starlight/components";
pg_orrery defines seven fixed-size base types and one SQL composite type that represent the core data structures of orbital mechanics. Each base type has a fixed on-disk size, a text I/O format for readability, and accessor functions for extracting individual fields.
pg_orrery defines eight fixed-size base types and one SQL composite type that represent the core data structures of orbital mechanics. Each base type has a fixed on-disk size, a text I/O format for readability, and accessor functions for extracting individual fields.
## tle
@ -270,6 +270,74 @@ FROM generate_series(1, 8) AS body_id,
---
## orbital_elements
**Size:** 72 bytes
Classical Keplerian orbital elements for comets and asteroids. Stores nine doubles: osculation epoch, perihelion distance, eccentricity, inclination, argument of perihelion, longitude of ascending node, time of perihelion passage, absolute magnitude H, and slope parameter G.
### Input Format
A parenthesized tuple of nine values:
```sql
SELECT '(2460605.5,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements;
```
The fields in order are: `(epoch_jd, q_au, e, inc_deg, omega_deg, Omega_deg, tp_jd, H, G)`.
<Aside type="note">
Angular fields (inclination, argument of perihelion, RAAN) are displayed in degrees but stored internally as radians — the same convention used by the `tle` type. H and G display as `NaN` when unknown.
</Aside>
### Constructor
| Function | Signature | Description |
|----------|-----------|-------------|
| `oe_from_mpc` | `oe_from_mpc(line text) → orbital_elements` | Parses one MPCORB.DAT fixed-width line. Converts MPC packed epoch, computes q and tp from (a, e, M). |
```sql
-- Parse (1) Ceres from an MPCORB.DAT line
SELECT oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
);
```
### Accessor Functions
| Function | Return Type | Description |
|----------|-------------|-------------|
| `oe_epoch(orbital_elements)` | `float8` | Osculation epoch (Julian date) |
| `oe_perihelion(orbital_elements)` | `float8` | Perihelion distance q (AU) |
| `oe_eccentricity(orbital_elements)` | `float8` | Eccentricity |
| `oe_inclination(orbital_elements)` | `float8` | Inclination (degrees) |
| `oe_arg_perihelion(orbital_elements)` | `float8` | Argument of perihelion (degrees) |
| `oe_raan(orbital_elements)` | `float8` | Longitude of ascending node (degrees) |
| `oe_tp(orbital_elements)` | `float8` | Time of perihelion passage (Julian date) |
| `oe_h_mag(orbital_elements)` | `float8` | Absolute magnitude H (NaN if unknown) |
| `oe_g_slope(orbital_elements)` | `float8` | Slope parameter G (NaN if unknown) |
| `oe_semi_major_axis(orbital_elements)` | `float8` | Semi-major axis a = q/(1-e) in AU. NULL for e ≥ 1. |
| `oe_period_years(orbital_elements)` | `float8` | Orbital period a^1.5 in years. NULL for e ≥ 1. |
```sql
-- Parse Ceres and extract key parameters
WITH ceres AS (
SELECT oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
) AS oe
)
SELECT oe_epoch(oe) AS epoch_jd,
oe_perihelion(oe) AS q_au,
oe_eccentricity(oe) AS ecc,
oe_inclination(oe) AS inc_deg,
oe_semi_major_axis(oe) AS a_au,
oe_period_years(oe) AS period_yr,
oe_h_mag(oe) AS h_mag
FROM ceres;
```
---
## observer_window
**Type:** SQL composite (variable-length)