Astronomy Engine adapter
This optional JavaScript provider implements every operation and vocabulary in the project’s
EphemerisAdapter contract. Astronomy Engine supplies physical ephemerides, observer parallax,
clocks and coordinate transformations; local mathematics supplies the astrology conventions.
Swiss remains the default provider until the host explicitly selects Astronomy Engine.
Supported behavior
| Area | Support |
|---|---|
| Bodies | Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto, Rahu and Ketu |
| Lunar nodes | Explicit mean or true/osculating nodes; Ketu is opposite Rahu |
| Zodiac | Tropical and sidereal |
| Ayanamsas | Lahiri, Raman, Krishnamurti and Fagan–Bradley |
| Observer | Geocentric or topocentric planetary positions, including elevation |
| Speeds | Optional longitude, latitude and distance derivatives |
| Houses | Placidus, Whole Sign, Equal, Sripati, Koch and Campanus; twelve cusps and eight named angles |
| Solar events | Hindu sunrise, next sunrise and optional sunset in the requested window |
The integration interval is Julian day TT 2415019.5–2488069.5 (1899-12-31 through 2100-01-01 TT). The leading day admits 1900 UTC despite negative Delta-T. This is narrower than Astronomy Engine’s theoretical range. Invalid requests, out-of-range instants and mathematically undefined geometry still return canonical errors. Placidus and Koch are undefined inside the polar circles; exact geographic poles and horizon tangencies have undefined house angles. The adapter never silently substitutes another house system.
Positions, zodiac and nodes
Planetary vectors include the engine’s aberration/light-time treatment and are transformed to true ecliptic coordinates of date. Observer requests use its topocentric equatorial calculation before that transformation. The engine special-cases its approximate lunar model; results are not numerically identical to Swiss apparent places.
Mean Rahu uses the secular lunar-node polynomial. True Rahu comes from the angular momentum of the instantaneous lunar position/velocity, with distance evaluated on the osculating ellipse at the node. Nodes remain geocentric orbital constructs in observer requests, matching the Swiss convention; they do not receive lunar-body parallax. Node latitude is zero. Ketu is exactly opposite Rahu with the same distance and longitude speed.
The four ayanamsas use mean-equinox J2000 reference offsets, IAU 2006 general precession and the engine’s longitude nutation. They are local approximations to the named conventions. Sidereal longitudes subtract the offset once. Speeds differentiate the complete requested geometry, including sidereal drift and observer rotation, using an 86.4-second half-step in TT. Longitude differences unwrap across 0°/360°; omitted speeds are null. Neighbouring derivative samples can lie just outside the advertised request interval, within the underlying engine’s range.
UTC approximates UT1, with Astronomy Engine’s default Espenak–Meeus Delta-T supplying TT.
Do not change its process-global SetDeltaTFunction when reproducible default results are
required. Source declarations identify engine 2.1.19 and local astrology algorithms v1,
actual clocks, observer origin and the applied offset. There are no native flags or data files.
precisionArcsec remains null: feature support and sampled agreement are not accuracy guarantees.
Houses and solar events
House calculations use the engine’s apparent sidereal time and true obliquity. Placidus solves semi-diurnal/nocturnal fractions with a bracketed root. Koch divides the MC degree’s rise-to-meridian time. Campanus uses intersections with prime-vertical division planes; its meridian half is chosen toward the zenith even inside polar circles. Sripati boundaries lie halfway between Porphyry quadrant-trisection centres. Whole Sign begins at the sign containing the Ascendant in the requested zodiac, including sidereal requests. Longitude-like angles shift with ayanamsa; ARMC remains equatorial. Elevation does not affect houses.
Hindu events use the geocentric Sun’s disc centre with zero ecliptic latitude and no refraction. Elevation and zodiac do not change this physical definition. The solver locates altitude extrema, then brackets horizon crossings between them, including short near-polar days. A window is at most 72 hours; both endpoints must lie within coverage. Missing events are null, sunset is omitted unless requested, and next sunrise is searched independently rather than manufactured as sunrise + 24 h. Timestamps are rounded to integer UTC milliseconds and clipped to the requested [start, end) window.
Use directly
import { AstronomyEngineAdapter } from '@astrology/ephemeris/astronomy-engine';
const adapter = new AstronomyEngineAdapter();
const result = await adapter.positions({
instant: '2000-01-01T12:00:00Z',
zodiac: 'sidereal',
ayanamsa: 'lahiri',
bodies: ['sun', 'moon', 'rahu', 'ketu'],
nodeType: 'true',
observer: { latitude: 19.076, longitude: 72.8777, elevationMeters: 14 },
includeSpeed: true,
});
const houses = await adapter.houses({
instant: '2000-01-01T12:00:00Z',
zodiac: 'sidereal', ayanamsa: 'lahiri',
observer: { latitude: 19.076, longitude: 72.8777 }, system: 'placidus',
});
This entry does not import Swiss, start workers or make network calls. Its async methods perform synchronous calculation and block the calling event loop. Astronomy Engine is MIT licensed; retain its notice. The encompassing ephemeris package still depends on Swiss: this import does not relicense Swiss code/data or remove that package dependency. No Swiss implementation was copied into the local astrology helpers; Swiss is used only as a comparison provider in tests/benchmarks.
Select through the Binder
import { createBinder } from '@astrology/ephemeris/node';
const binder = await createBinder({
nasa: { userAgent: 'my-chart-app/1.0 (ops@example.com)' },
astronomyEngine: true,
resolveJulianDayTt: epochMilliseconds => timeService.julianDayTt(epochMilliseconds),
routing: { primaryProvider: 'astronomy-engine' },
});
timeService is the host’s tested TT resolver. This factory still initializes Swiss and NASA,
so its pinned Swiss files remain necessary. For injected adapters, pass
AstroBinder.create({ swiss, nasa, astronomyEngine: adapter }, options).
Registration alone preserves Swiss as primary. Once selected, Astronomy Engine handles positions,
houses and solar events itself; it does not fall back to Swiss for missing functionality.
Normalization retains provider identity and the Binder adds existing astrology facts.
Benchmark and verification
From the repository root:
pnpm build:ephemeris
pnpm setup:ephemeris # only if pinned Swiss files are missing
pnpm --filter @astrology/ephemeris benchmark:astronomy-engine \
--samples 100 --warmup 5 --output /tmp/astronomy-engine-benchmark.json
The recorded JSON retains environment, provider versions, Swiss-file hashes, individual latency samples and drift observations. The runner compares the public adapters and a reused internal Swiss engine as a diagnostic control. That control bypasses per-call worker isolation and file verification; it is not a supported production adapter.
Each workload uses 100 serial requests after five warmups per mode, rotating execution order and varying the same instants in six-hour increments from 2024-01-01. Workloads cover two/ten/twelve bodies, speeds, sidereal and topocentric requests, each of the six houses, and 48-hour Hindu event windows. Calls include validation, with no Binder, normalization or application result cache. Internal ephemeris caches warm up. Throughput is serial completed calls per measured second, not server capacity under concurrent load. First-call timings exclude module imports and do not guarantee cold OS file caches. Run without simultaneous builds/tests for useful timings.
The planetary drift sample retains 440 body comparisons at 44 dates from 1900 through late 2099. It includes same-UTC adapter differences plus raw Astronomy Engine geometry at Swiss’s declared TT, separating time-model offsets from position-model differences. Feature comparisons additionally cover all four ayanamsas, both node types, topocentric/sidereal positions, all six house systems across equatorial, northern, southern and polar locations, and solar events. Matched polar house errors are recorded explicitly. These samples establish agreement only at the tested cases.
Recorded results
Measured 2026-10-27, Apple M4, Darwin 25.5.0 arm64, Node 26.7.0. The table shows p50 / p95 milliseconds per request, with 100 measured calls per workload and mode.
| Workload | Astronomy Engine adapter | Existing Swiss adapter | Reused Swiss engine control |
|---|---|---|---|
| Ten tropical bodies with speeds | 0.250 / 0.335 | 33.520 / 35.436 | 0.052 / 0.062 |
| Twelve sidereal bodies with speeds | 0.309 / 0.373 | 33.544 / 35.388 | 0.071 / 0.077 |
| Twelve sidereal/topocentric bodies with speeds | 0.315 / 0.410 | 33.738 / 36.302 | 0.165 / 0.183 |
| Sidereal Placidus houses | 0.035 / 0.050 | 33.449 / 35.689 | 0.047 / 0.051 |
| Hindu solar events, 48 hours | 1.122 / 1.237 | 33.715 / 35.804 | 0.105 / 0.126 |
The remaining five house-system workloads and all individual samples are in the JSON. Across all twelve workloads and three modes it contains 3,600 latency measurements. The reused Swiss control is faster for planetary positions and solar events. Much of the difference between public adapters is Swiss’s per-call process startup and file verification, not faster ephemeris mathematics.
The feature matrix uses four dates (1900, 2000, 2024 and 2099), all named ayanamsas, both node types and seven locations. It records 16 ayanamsa, 16 node-body, 168 house and 96 topocentric body comparisons, plus three solar windows. Sixteen house requests have matching polar-undefined errors; the other 152 have matching supported geometry.
| Measured feature | Maximum absolute difference versus Swiss in this sample |
|---|---|
| Ayanamsa offset | 0.108 arcseconds |
| Mean node longitude | 0.133 arcseconds |
| True node longitude | 11.806 arcseconds |
| House cusp longitude | 32.893 arcseconds |
| Named house angles | 11.775 arcseconds |
| Topocentric/sidereal body longitude | 55.340 arcseconds |
The separate 44-date geocentric sample retains the largest Moon longitude difference of 64.771 arcseconds at the end of 2099 with same-UTC adapter inputs; matching the reference TT reduces the sampled Moon maximum to 2.916 arcseconds. Provider TT differs by up to 109.469 seconds. Validate boundary-sensitive astrology against its own acceptable tolerances.
The London reference has a documented event-search discrepancy: Swiss’s repeated next-sunrise search reports 2000-01-03 08:12:05.994Z, whereas a fresh Swiss search for that day reports 08:12:16.007Z. Astronomy Engine’s independent horizon root is 08:12:16.330Z. Tests check fresh-search agreement and consistency across clipped windows rather than copying that discrepancy.
Adapter tests use independent C swetest fixtures for positions, speeds, named sidereal offsets,
house cusps/angles and solar events, plus real Swiss comparisons across locations. They exercise
node antipodes, observer isolation, longitude wrap, retrograde motion, polar failures/no-event
windows, public import independence and Binder/factory routing for every operation.
The workspace build, package typechecks and documentation links/render parsing passed. Relevant
suites passed 125 ephemeris, 20 runtime and 10 drift-validator tests, including
33 focused Astronomy Engine tests. The aggregate check:packages command still stops at an
existing Vedic engine-to-calculator boundary violation in
packages/vedic/src/application/engine/engine.ts. Aggregate test:packages stops at an existing
logger test expecting @astrology/api.requests while its input uses
@astrology/astrology-api.requests. Neither unrelated file was changed.
Related code and references
- Adapter entry
- Time, declaration and ayanamsas
- Lunar nodes
- House geometry
- Solar event roots
- Benchmark runner
- Reference and integration tests
- Astronomy Engine APIs and license
- IERS Conventions, Chapter 5 — precession conventions
- Jean Meeus, Astronomical Algorithms, second edition, chapter 47 — mean lunar-node polynomial