astropy / astropy/astropy

Add support for direction representations

Open
#20,356 0 comments 1 reaction 0 assignees View on GitHub
coordinates Feature Request
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.