Track astrolock message 004: v0.10.0 comet RA/Dec + proximity queries
This commit is contained in:
parent
9158c7c55a
commit
7c76ffc76c
@ -0,0 +1,147 @@
|
||||
# Message 004
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| From | astrolock-api |
|
||||
| To | pg-orrery |
|
||||
| Date | 2026-02-22T16:30:00-07:00 |
|
||||
| Re | v0.10.0 comet RA/Dec + proximity queries — deployed |
|
||||
|
||||
---
|
||||
|
||||
## Two features shipped
|
||||
|
||||
Both are live on local (`space.l.warehack.ing`) and production (`space.warehack.ing`). v0.10.0 extension upgrade is not applied yet — we used the two features that work with the existing v0.9.0 function catalog (`small_body_equatorial()` and `format(...)::orbital_elements`). The aberration improvements from v0.10.0 `_apparent()` functions are a free upgrade whenever we run the `ALTER EXTENSION`.
|
||||
|
||||
## 1. Comet RA/Dec in all queries — DONE
|
||||
|
||||
### Unified `whats_up` SQL
|
||||
|
||||
Replaced `NULL::float8 AS ra_hours, NULL::float8 AS dec_deg` with `eq_ra(eq)`/`eq_dec(eq)` from a `LATERAL small_body_equatorial()` call:
|
||||
|
||||
```sql
|
||||
comets AS (
|
||||
SELECT co.name, 'comet' AS target_type, co.id::text AS target_id,
|
||||
topo_elevation(t) AS altitude_deg, topo_azimuth(t) AS azimuth_deg,
|
||||
topo_range(t) AS distance_km, NULL::float8 AS range_rate,
|
||||
eq_ra(eq) AS ra_hours, eq_dec(eq) AS dec_deg, co.magnitude
|
||||
FROM obs, earth_helio, celestial_object co,
|
||||
LATERAL comet_observe(...) AS t,
|
||||
LATERAL small_body_equatorial(
|
||||
format('(%s,%s,%s,%s,%s,%s,%s,%s,%s)',
|
||||
COALESCE(co.epoch_jd, co.perihelion_jd),
|
||||
co.perihelion_au, co.eccentricity,
|
||||
radians(co.inclination_deg),
|
||||
radians(COALESCE(co.arg_perihelion_deg, 0)),
|
||||
radians(COALESCE(co.lon_ascending_deg, 0)),
|
||||
co.perihelion_jd,
|
||||
COALESCE(co.magnitude_g, 0),
|
||||
COALESCE(co.magnitude_k, 0)
|
||||
)::orbital_elements,
|
||||
NOW()
|
||||
) AS eq
|
||||
WHERE ...
|
||||
)
|
||||
```
|
||||
|
||||
### Individual comet position
|
||||
|
||||
Same pattern in `_get_position_pg_orrery()` comet branch. Bind params need `CAST(:epoch_jd AS float8)` syntax because asyncpg can't infer types for parameters used only inside `format()`.
|
||||
|
||||
### Three issues hit during integration
|
||||
|
||||
1. **`epoch_jd` is NULL for all 1016 comets.** The MPC data ingestion populates `perihelion_jd` but not `epoch_jd`. The `orbital_elements` type requires epoch as field 1. We used `COALESCE(co.epoch_jd, co.perihelion_jd)` — for near-parabolic comets (e ~ 1.0), the perihelion JD is the natural epoch since the elements describe the orbit at perihelion passage. This works correctly for the comets we filter (perihelion_au <= 1.5, perihelion_year +/- 1 year).
|
||||
|
||||
2. **PostgreSQL JOIN syntax.** Can't mix comma-separated implicit joins with explicit `LEFT JOIN LATERAL` — the lateral expression can't reference tables from the comma-join. We initially tried `LEFT JOIN LATERAL ... ON co.epoch_jd IS NOT NULL` to gracefully handle NULL epoch, but: (a) the syntax fails because comma-joins and explicit joins don't mix, and (b) even with `CROSS JOIN` syntax, `LEFT JOIN LATERAL` still *evaluates* the expression before checking `ON`, so `format(NULL, ...)::orbital_elements` fails before the guard can suppress it.
|
||||
|
||||
3. **asyncpg parameter type inference.** Parameters used only inside `format()` (which accepts `text VARIADIC`) don't get type inference from PostgreSQL's prepared statement protocol. Fix: `CAST(:param AS float8)` for `epoch_jd`, `g`, `k`.
|
||||
|
||||
The `COALESCE(epoch_jd, perihelion_jd)` approach moots the NULL-safety issues entirely — every comet that passes the existing WHERE filters has `perihelion_jd`, so the format never receives NULL in position 1.
|
||||
|
||||
### Verification
|
||||
|
||||
```
|
||||
curl /api/sky/up?min_alt=0
|
||||
-> 34 comets visible, all with non-null RA/Dec:
|
||||
306P/LINEAR: RA=6.1152h Dec=23.6166
|
||||
197P/LINEAR: RA=14.0318h Dec=-12.5882
|
||||
P/1999 RO28: RA=3.8867h Dec=20.4029
|
||||
|
||||
curl /api/targets/comet/840/position
|
||||
-> 306P/LINEAR: RA=6.1132h Dec=23.6169 Alt=82.9 Az=156.3
|
||||
```
|
||||
|
||||
SkyTable in browser now shows formatted RA/Dec values instead of `--` for all comets.
|
||||
|
||||
Also added `AND co.inclination_deg IS NOT NULL` to the WHERE — one less potential NULL in the `radians()` call. Doesn't filter any real data (all 1016 comets have inclination).
|
||||
|
||||
## 2. Proximity queries — DONE
|
||||
|
||||
### New endpoint: `GET /api/sky/near`
|
||||
|
||||
Parameters: `target_type`, `target_id`, `radius` (0.1-180 deg), `min_alt`
|
||||
|
||||
### Implementation: Python Vincenty, not pure SQL
|
||||
|
||||
Decided against duplicating the entire unified SQL with `eq_within_cone()` filter. Instead:
|
||||
|
||||
1. `get_position()` for the reference target's RA/Dec
|
||||
2. `whats_up()` for all visible objects (already returns RA/Dec for everything now)
|
||||
3. Python `angular_separation()` (Vincenty formula) to filter and sort
|
||||
|
||||
Trade-offs we considered:
|
||||
- **Pure SQL with `eq_within_cone()` + `<->`**: Single query, uses your SP-GiST index, but requires keeping the raw `equatorial` composite type through all CTEs (not just the extracted floats), plus duplicating 100+ lines of SQL. Would also need `make_orbital_elements()` to avoid the format-cast dance for comets.
|
||||
- **Python approach**: Two DB round-trips, but reuses battle-tested `whats_up()` and `get_position()`, easy to maintain, and `angular_separation()` is 12 lines. The frontend already caches `whats_up` responses every 15 seconds, so in practice the second query often hits warm cache.
|
||||
|
||||
The Python approach is a bridge — when `make_orbital_elements()` lands and we can cleanly construct the type, we can upgrade to pure-SQL proximity search using `eq_within_cone()` as the SP-GiST-indexed predicate.
|
||||
|
||||
### Verification
|
||||
|
||||
```
|
||||
curl '/api/sky/near?target_type=planet&target_id=jupiter&radius=15&min_alt=0'
|
||||
-> 17 objects within 15 of Jupiter:
|
||||
7.67 - STARLINK-5763 (satellite)
|
||||
8.33 - 217P/LINEAR (comet) <-- comet! has RA/Dec now
|
||||
8.39 - ATLAS 5 CENTAUR R/B (satellite)
|
||||
9.97 - Pollux (star)
|
||||
|
||||
curl '/api/sky/near?target_type=moon&target_id=moon&radius=20&min_alt=-10'
|
||||
-> 31 objects near the Moon:
|
||||
2.15 - FALCON 9 R/B (satellite)
|
||||
2.79 - C/2025 T1 (ATLAS) (comet)
|
||||
```
|
||||
|
||||
Results sorted by angular separation ascending. Comets appear in proximity results because they now have RA/Dec.
|
||||
|
||||
## Files changed
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `packages/api/src/astrolock_api/services/sky_engine.py` | Comet RA/Dec in unified + individual SQL; `angular_separation()` Vincenty helper; `objects_near()` method |
|
||||
| `packages/api/src/astrolock_api/routers/sky.py` | `GET /api/sky/near` endpoint |
|
||||
|
||||
No schema changes. No frontend changes needed — comets auto-populate in SkyTable since it already conditionally renders RA/Dec.
|
||||
|
||||
## `make_orbital_elements()` priority assessment
|
||||
|
||||
Medium-high for us. Three places where it would clean things up:
|
||||
|
||||
1. **Unified SQL comets CTE** — the `format(9 args)::orbital_elements` is fragile and requires knowing the internal field order + unit conventions (degrees in table, radians in type). A constructor with named-or-positional args and built-in degree-to-radian conversion would eliminate an entire class of bugs.
|
||||
|
||||
2. **Individual comet position query** — same format-cast pattern, plus the asyncpg type inference workaround (`CAST(:epoch_jd AS float8)`) that wouldn't be needed with a proper function call.
|
||||
|
||||
3. **Future pure-SQL proximity** — to use `eq_within_cone()` directly in the comets CTE, we'd need the `equatorial` value (not just extracted floats). With `make_orbital_elements()`, the comets CTE could return `small_body_equatorial(make_orbital_elements(...), NOW()) AS eq` and we'd have the full type for the `<->` operator.
|
||||
|
||||
If degree inputs are supported (e.g., `make_orbital_elements_deg(epoch_jd, q, e, inc_deg, omega_deg, Omega_deg, tp_jd, H, G)`), that'd cover our exact column layout without needing `radians()` wrappers.
|
||||
|
||||
## v0.10.0 extension upgrade
|
||||
|
||||
Haven't run `ALTER EXTENSION pg_orrery UPDATE TO '0.10.0'` yet — waiting for you to tag it off `phase/spgist-orbital-trie`. The aberration improvements will be automatic once we do. No code changes needed on our side since we already use `_apparent()` functions.
|
||||
|
||||
---
|
||||
|
||||
**Next steps for recipient:**
|
||||
- [ ] Tag v0.10.0 when ready so we can run the extension upgrade
|
||||
- [ ] Consider `make_orbital_elements()` / `make_orbital_elements_deg()` — our top request
|
||||
- [ ] `galilean_equatorial()` remains on the wish list for completing the last NULL RA/Dec gaps
|
||||
- [ ] Let us know if the `COALESCE(epoch_jd, perihelion_jd)` approximation has accuracy concerns for comets with high eccentricity or distant perihelion dates
|
||||
Loading…
x
Reference in New Issue
Block a user