JPL DE Ephemeris
pg_orrery v0.3.0 adds optional support for JPL Development Ephemeris files (DE440/DE441), bringing positional accuracy from VSOP87’s ~1 arcsecond to DE441’s ~0.1 milliarcsecond. The upgrade path is designed so that nothing changes unless you opt in.
When you need DE
Section titled “When you need DE”VSOP87 is sufficient for most purposes --- visual observation planning, rise/set times, conjunction identification, telescope pointing at optical wavelengths. You need DE when:
- GHz dish pointing. A 10m dish at 10 GHz has a ~0.1 degree beamwidth. VSOP87’s 1 arcsecond error is well within the beam, but systematic campaigns benefit from the tighter DE error budget.
- Precision astrometry. Comparing CCD field positions against computed planet positions at the sub-arcsecond level.
- Occultation timing. Predicting when a planet or asteroid will occult a star requires milliarcsecond precision in the planet’s geocentric position.
- Interplanetary mission design. Lambert transfer C3 values are sensitive to planet position accuracy. DE441 eliminates VSOP87 as an error source.
-
Obtain a DE file. Download DE441 (~3.1 GB, covers -13200 to +17191) or DE440 (~115 MB, covers 1550 to 2650) from JPL:
Terminal window # DE440 (smaller, covers 1550-2650, sufficient for most uses)curl -O https://ssd.jpl.nasa.gov/ftp/eph/planets/Linux/de440/linux_p1550p2650.440# DE441 (full range, large file)curl -O https://ssd.jpl.nasa.gov/ftp/eph/planets/Linux/de441/linux_m13000p17000.441 -
Place the file where PostgreSQL can read it:
Terminal window sudo mkdir -p /var/lib/postgres/pg_orrerysudo cp linux_p1550p2650.440 /var/lib/postgres/pg_orrery/de440.binsudo chown postgres:postgres /var/lib/postgres/pg_orrery/de440.bin -
Set the GUC (requires superuser):
ALTER SYSTEM SET pg_orrery.ephemeris_path = '/var/lib/postgres/pg_orrery/de440.bin';SELECT pg_reload_conf(); -
Verify:
SELECT * FROM pg_orrery_ephemeris_info();You should see
provider = 'JPL_DE'with the file’s JD range and version.
Using DE functions
Section titled “Using DE functions”Every VSOP87 function has a _de() counterpart with the same signature:
-- Always VSOP87, always IMMUTABLESELECT topo_azimuth(planet_observe(4, '40.0N 105.3W 1655m'::observer, now())) AS mars_az;-- Uses DE if configured, falls back to VSOP87 otherwise. STABLE.SELECT topo_azimuth(planet_observe_de(4, '40.0N 105.3W 1655m'::observer, now())) AS mars_az;The _de() variants share the same parameter types, return types, and body ID conventions. You can swap between them by adding or removing _de from the function name.
All planets with DE positions
Section titled “All planets with DE positions”SELECT body_id, CASE body_id WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus' WHEN 4 THEN 'Mars' WHEN 5 THEN 'Jupiter' WHEN 6 THEN 'Saturn' WHEN 7 THEN 'Uranus' WHEN 8 THEN 'Neptune' END AS planet, round(topo_azimuth(planet_observe_de(body_id, '40.0N 105.3W 1655m'::observer, now()))::numeric, 4) AS az, round(topo_elevation(planet_observe_de(body_id, '40.0N 105.3W 1655m'::observer, now()))::numeric, 4) AS elFROM unnest(ARRAY[1,2,4,5,6,7,8]) AS body_idWHERE topo_elevation(planet_observe_de(body_id, '40.0N 105.3W 1655m'::observer, now())) > 0ORDER BY el DESC;Moon via DE
Section titled “Moon via DE”SELECT round(topo_azimuth(moon_observe_de('40.0N 105.3W 1655m'::observer, now()))::numeric, 2) AS az, round(topo_elevation(moon_observe_de('40.0N 105.3W 1655m'::observer, now()))::numeric, 2) AS el, round(topo_range(moon_observe_de('40.0N 105.3W 1655m'::observer, now()))::numeric, 0) AS range_km;Lambert transfers with DE positions
Section titled “Lambert transfers with DE positions”-- Earth-Mars transfer with DE-quality planet positionsSELECT * FROM lambert_transfer_de(3, 4, '2026-05-01 00:00:00+00', '2027-01-15 00:00:00+00');Cross-validation: DE vs VSOP87
Section titled “Cross-validation: DE vs VSOP87”Compare DE and VSOP87 results to see the sub-arcsecond difference:
SELECT body_id, round(helio_distance(planet_heliocentric(body_id, '2024-06-21 12:00:00+00'))::numeric, 10) AS vsop87_r, round(helio_distance(planet_heliocentric_de(body_id, '2024-06-21 12:00:00+00'))::numeric, 10) AS de_r, round((helio_distance(planet_heliocentric_de(body_id, '2024-06-21 12:00:00+00')) - helio_distance(planet_heliocentric(body_id, '2024-06-21 12:00:00+00')))::numeric * 149597870.7, 2) AS diff_kmFROM generate_series(1, 8) AS body_id;Accuracy comparison
Section titled “Accuracy comparison”| Provider | Theory | Accuracy | Volatility | Data Source |
|---|---|---|---|---|
| VSOP87 | Fourier series (Bretagnon 1988) | ~1 arcsecond (inner planets), up to 40 arcseconds (Neptune, +/- 4000 yr) | IMMUTABLE | Compiled-in coefficients |
| ELP2000-82B | Fourier series (Chapront 1983) | ~2 arcseconds (longitude) | IMMUTABLE | Compiled-in coefficients |
| JPL DE440 | Numerical integration | ~0.1 milliarcsecond | STABLE | External binary file (115 MB) |
| JPL DE441 | Numerical integration | ~0.1 milliarcsecond | STABLE | External binary file (3.1 GB) |
Diagnostics
Section titled “Diagnostics”The pg_orrery_ephemeris_info() function reports the current state:
SELECT * FROM pg_orrery_ephemeris_info();| Column | Type | Description |
|---|---|---|
provider | text | 'VSOP87' or 'JPL_DE' |
file_path | text | Path to DE file (empty if VSOP87-only) |
start_jd | float8 | First Julian Date in the DE file |
end_jd | float8 | Last Julian Date in the DE file |
version | int4 | DE version number (440, 441, etc.) |
au_km | float8 | AU value from the DE header |
When no DE file is configured, provider returns 'VSOP87' and the remaining columns are defaults.
Fallback behavior
Section titled “Fallback behavior”DE functions never fail silently. The fallback rules:
- No GUC set (default): Fall back to VSOP87/ELP2000-82B silently. No NOTICE, no overhead.
- GUC set, file works: Use DE positions.
- GUC set, query-time failure (JD out of range, I/O error): Emit
NOTICEexplaining the fallback, then use VSOP87/ELP2000-82B.
This means _de() functions always return a valid result. They never abort due to DE-specific issues --- at worst, they degrade to VSOP87 accuracy with a NOTICE explaining why.