Add support for direction representations
- Dominant language
- Python
- Stars
- 5.3k
- Forks
- 2.2k
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 74
Description
### What is the problem this feature will solve?
Astropy's coordinate machinery is built around points (positions), not directions (free vectors). A direction - a vector with the same units as a coordinate (e.g. meters) that transforms under a frame change by rotation only, with no translation - has no first-class representation in astropy.
Currently, the only ways to represent a direction are workarounds, each with serious flaws:
- **CartesianRepresentation** treats the vector as a point, so frame transforms apply the origin shift (translation), which is wrong for a direction.
- **UnitSphericalRepresentation** only handles unit-length directions on the sphere, and is still point-like in its transform behavior.
- **CartesianDifferential** transforms with rotation-only, but is semantically a velocity. Under time-dependent transforms (e.g. ITRS → GCRS, where the frames rotate relative to each other), a differential picks up a spurious position-dependent term.
This forces users to either hand-roll rotation-matrix transforms, or misuse velocities/positions and silently get incorrect results.
I want to represent a direction vector (e.g. a line-of-sight, a surface normal, a relative displacement) in a given frame, carry it through frame transforms, and have it transform correctly - i.e. by rotation only, with no origin shift and no frame-rotation-rate term.
Concrete examples where this is needed:
- Line-of-sight / pointing vectors: a unit direction from a sensor toward a target, transformed between an Earth-fixed frame (ITRS) and an inertial frame (GCRS/ICRS). Using a CartesianDifferential here injects a spurious ~465 m/s Earth-rotation term; using a CartesianRepresentation applies an origin shift that a direction should not have. Workaround is to transform two points and take the difference after the transformation, not ideal but kind of works.
- Surface normals / local verticals: the geodetic normal at a point on the WGS84 ellipsoid, transformed between frames. This is a direction, not a position.
- Relative displacements: the vector between two points, which is translation-invariant and should transform by rotation only.
### Describe the desired outcome
I would like astropy to provide a first-class **direction** (free-vector) concept that:
- Carries units like a coordinate (e.g. meters, or dimensionless for unit vectors).
- Transforms between frames by **rotation only** — no origin shift, no frame-rotation-rate term.
- Integrates with the existing frame/transform machinery so users don't have to extract rotation matrices manually.
**Draft design / API suggestion:**
A new representation class, e.g. `CartesianDirection`, analogous to `CartesianRepresentation` but with transform semantics of a free vector:
```python
from astropy.coordinates import CartesianDirection, ITRS, GCRS
# A direction vector with units (e.g. a line-of-sight in meters)
d = CartesianDirection([1000.0, 2000.0, 3000.0], unit=u.m)
# Transform between frames: rotation only, no shift, no rate term
d_gcrs = d.transform_to(GCRS())
```
The key behavioral difference from `CartesianRepresentation`:
| | `CartesianRepresentation` (point) | `CartesianDirection` (direction) |
|---|---|---|
| Transform | `p' = R·p + t` | `d' = R·d` |
This could be implemented either as a new representation class, or as a flag/attribute on existing representations that tells the transform machinery to drop the translation (and any $\dot{R}$ term). A `UnitCartesianDirection` variant (unit-length) would cover the common line-of-sight case.
### Additional context
This is a known, acknowledged limitation of astropy's architecture. The most relevant existing discussion is:
- #12731 (open, Feature Request). The maintainer (mhvk) explicitly notes that treating position and velocity as both "simply relative to a given origin" is incorrect and that this is *"pretty much baked in at the deepest level (with our coordinate representations and differentials)."* This is the same underlying conflation of points vs. directions.
Related issues:
- #12594 (open, Bug) — the motivating bug for #12731.
- #17951 (open, Bug) — how coordinates without full position info behave under origin-shifting transforms.
- #10983 related frame-transform discussion.
Disclaimer: Parts of this description was generated using AI.
Contributor guide
Research direction
Start by reading the existing CartesianRepresentation, UnitSphericalRepresentation, and CartesianDifferential behavior in the coordinate frame-transform machinery, then review related issues #12731, #12594, #17951, and #10983. The work is complete when a first-class direction representation carries units and transforms between frames by rotation only, without origin shifts or frame-rotation-rate terms.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100