# Position Angle Frame Transformations #3093

Open
opened this issue Nov 10, 2014 · 19 comments

Contributor

### sargas commented Nov 10, 2014

 This is a feature request to convert angles between coordinate systems with SkyCoord. An example by @cdeil in #2311 : ```from astropy.coordinates import ICRS, Galactic, Angle coordinate = ICRS(0, 0, ('deg', 'deg')) coordinate.position_angle = Angle(42, 'deg') print(coordinate.to(Galactic).position_angle)``` This example used the `ICRS` object from the old API, so I'd like to propose two alternatives: `BaseCoordinateFrame` carries a class method, perhaps called `transform_angle_to`, that takes a `SkyCoord`, `Angle`, and frame and returns another `Angle` in the new frame. `SkyCoord` contains an angle parameter that gets transformed (if set) when `SkyCoord.transform_to` is called. I'm inclined against this one, since it effectively turns points (`SkyCoord`s) into vectors. Also, since the transformation of position angles across coordinate systems would be one of the main use cases, this might require a note in the docs that the `position_angle` function is not related to this functionality. This functionality would be appreciated to avoid some more wcs-handling for astropy/pyregion#43. Specifically, the ability to transform an angle measured off a point (which may not have the same coordinate system) to another coordinate system is needed. I'm not familiar enough with WCS transformations to do this myself; I've seen @cdeil's gist attempt to find the new angle by using a small offset and pyregion's existing code to estimate it the new basis vectors with several transformations back and forth. I haven't found how the NED calculator makes the transformation.
Member

### astrofrog commented Nov 10, 2014

 Can we do option 1 but not bother with making it a class method? We could simply use a standalone function, right?
Contributor Author

### sargas commented Nov 10, 2014

 I like that approach. How about something like this: (to use a mix of java syntax): ```Angle astropy.coordinates.transform_angle(SkyCoord origin, Angle angle, BaseCoordinateFrame new_frame) """ Convert angle (defined as east of north from origin in origin's frame) to new_frame """```
Member

### astrofrog commented Nov 10, 2014

 @sargas - yes, that seems fine. Thinking about it more, this could be a method of Angle actually. Note that for the first argument, it doesn't have to be `SkyCoord`, it could also be e.g. an `ICRS` frame with data (which is the underlying API, `SkyCoord` is just a convenience).
Contributor

### mhvk commented Nov 10, 2014

 Similar functionality will be needed for postiion differences between two `SkyCoord` (and for proper motions, etc.). I'd guess we want this as part of coordinates generally rather than `Angle` (which is really just a `Quantity` with some nice input parsing). Maybe easiest to start with a function... (CC @eteq, who may have thought much more about this).
Member

### astrofrog commented Nov 10, 2014

 @mhvk - positions between two `SkyCoord` is already implemented, right?
Contributor

### mhvk commented Nov 10, 2014

 Sorry, I was unclear: I meant similar functionality is needed to transform position differences between two `SkyCoord` from one system to another. Position angle is just one aspect where the vector from one `SkyCoord` is in the sky and where one is interested in the angle only, not its norm.
Member

### eteq commented Nov 10, 2014

 @astrofrog @mhvk @sargas - I don't quite understand what is being discussed here. Some of the functionality that's related here: #2487 , which gives the position angle between two `SkyCoord`s. `separation` and `separation_3d`, which gives the on-sky angle between two `SkyCoord`s, or the 3d (unitful) distance. So is the request here to do sort of the inverse of one or both of these? That is, get a skycoord that's some position angle offset from another, or offset by a specific RA and Dec? I'm confused mostly by the use of the word "transform" - in `SkyCoord`, "transform" means "convert from one frame to another", which seems to be different from what you're saying (but maybe not?).
Contributor Author

### sargas commented Nov 10, 2014

 @astrofrog - ah, I see how it's done in `SkyCoord.separation`. Perhaps just `Angle.transform_to(self, origin, new_frame)`, with a return-ed `Angle` @eteq - I think my request is different then @mhvk, but the idea is this: You have a point, and an angle measured from this point that depends on the coordinate system. The position angle between two `SkyCoord` is one example. This angle is defined in a certain direction, so it is frame dependent. `SkyCoord.transform_to` will change the point to the new coordinate system. The angle must also change numerically, but there is no function in astropy to calculate this. If the angle is given by the position angle between two `SkyCoord`, then you could transform them and re-run `SkyCoord.position_angle` to get the new angle.
Contributor Author

### sargas commented Nov 10, 2014

 As an example, let's say you have a point at (0,0) in FK5, with a vector going due west (so, measuring from east of north, described by this point and `Angle('270d')`). If I transform to a coordinate frame that is the exact same as FK5 but with the poles reversed, the angle described in this new frame should be `Angle('90d')` Another way to state it is that if star A is exactly due west of star B in the FK5 coordinate system (i.e., `star_a.position_angle(star_b) == Angle('270d')`), then after converting both stars to an upside-down FK5 system I know I should get a position angle of `Angle('90d')` regardless of where star B was (since it is now due east).
Contributor

### mhvk commented Nov 10, 2014

 @eteq - sorry for the confusion. I think in the end @sargas and I would like to do similar things; in my case, suppose I have a position and proper motion in ICRS, I'd like to be able to transform both to Galactic, but so far, as I understood it, I can easily transform the coordinate only. Since, of course, the magnitude of the proper motion does not change, in the end all I really need is only the transformed position angle on the sky.
Member

### eteq commented Nov 10, 2014

 @sargas - so is the following snippet what you're asking about? ``````sc1 = SkyCoord(0*u.deg,0*u.deg,frame='fk5') sc2 = SkyCoord(270*u.deg,0*u.deg,frame='fk5') pa1 = sc1.position_angle(sc2) assert pa1.deg == 270 sc1_t = sc1.transform_to(UpsideDownFK5) sc2_t = sc2.transform_to(UpsideDownFK5) pa2 = sc1_t.position_angle(sc2_t) assert pa2.deg == 90 `````` (Of course you'd have to define the transform to `UpsideDownFK5` somewhere, but I think this gets the idea across)
Member

### eteq commented Nov 10, 2014

 @mhvk - re: proper motion, that's definitely something we need to consider a "first class citizen" for `coordinates` (@adrn has been pushing this repeatedly), but I think we need to think carefully about the interface for proper motions (and radial velocities, too, to be fully 3D). But I'm I right you're thinking about this as a "lower-level" implementation for this?
Contributor Author

### sargas commented Nov 10, 2014

 @eteq Yes. This issue is about having a function that takes `sc1`, `pa1`, and `UpsideDownFK5` and returns `pa2` One possible implementation (basically what's done in https://gist.github.com/cdeil/10597172 ) is to generate an `sc2` that gives the right position angle. In @mhvk 's case, he/she would need to get `pa2` and `sc1_t` from `sc1`, `pa1`, and `UpsideDownFK5`. `sc1_t` is already obtained without `sc2`, as shown in your snippet.
Contributor Author

### sargas commented Nov 10, 2014

 Actually, I think I should ask clarification of @mhvk's case, since proper motion should be a frame-independent Angle/time AFAIK. Are you trying to transform the direction of the proper motion?
Contributor

### mhvk commented Nov 11, 2014

 @sargas - I was simply thinking of converting μ(α), μ(δ) to μ(lᴵᴵ), μ(bᴵᴵ); you're right that of course |μ| remains the same, but the angle changes -- in just the same way your position angle would change, which is why I brought it up as an example. @eteq - I was indeed only thinking of the low-level implementation, which requires to have a way to transform position angles -- I'm sorry that in the end my example only seems to have confused matters! But with something along the lines of what @sargas wants in place, proper motions will be all the easier to do.
Member

### jwoillez commented Nov 11, 2014

 Can this request be generalized to transform of vectors? Connected use cases could be: what does an instrument axis (telescope axis, interferometer baseline) looks like on sky? what is the direction of celestial (ICRS) north on your instrument? How do you attach a compass rose to you images? Seems connected to SOFA/ERFA for the ICRF to ITRF transform.
Member

### eteq commented Nov 19, 2014

 @sargas - ok, so would something like this work for you? ``````sc1 = SkyCoord(0*u.deg,0*u.deg,frame='fk5') pa1 = 270*u.deg sc2 = sc1.skycoord_from_position_angle(pa1) #there's probably a better name for this... sc1_t = sc1.transform_to(UpsideDownFK5) sc2_t = sc2.transform_to(UpsideDownFK5) pa2 = sc1_t.position_angle(sc2_t) `````` That is, the only thing that new that would need to be added to do this would be `skycoord_from_position_angle` (which could be useful in other circumstances, anyway). The direction I'm coming at is that we don't really want to duplicate all the convenience functions with functions that just do the transforms and then the convenience functions - it's ok to just say that you have to do the transforms yourself. I think this would end up more as more readable code because it avoids the problem you mentioned in the initial issue comment about mixing the vector/point concepts. But if you think that's too repetitive, maybe a utility function that just does a transform and a method in one go is the solution? So that would look something like the top three lines from the above, then the bottom three condensed into a helper function like `transform_then_do([sc1, sc2], UpsideDownFK5, sc1.position_angle)`. That's almost as readable as the above, but more convinient if you're going to repeat many times... @jwoillez - For "generalized transform of vectors", we definitely want to piggy-back on the work with the frames. But I think we need to think hard about the best way to do this in a way that won't be confusing to users by mixing the vector and point concepts too much - this includes the stuff @mhvk is saying about proper motions. I'm thinking this is a major goal for the version after 1.0, because it would be much more useful once we have the AltAz <-> ICRS conversions in place.
Contributor Author

### sargas commented Nov 23, 2014

 @eteq - Originally I was thinking this could be implemented analytically without approximating the temporary point (the result of the `skycoord_from_position_angle` function you give). As a stop-gap measure, I implemented this in an earlier pyregion PR: ``````def _estimate_angle(angle, origin, new_frame, offset=1e-7): angle_deg = angle*np.pi/180 newlat = offset * np.cos(angle_deg) + origin.data.lat.degree newlon = (offset * np.sin(angle_deg) / np.cos(newlat * np.pi/180) + origin.data.lon.degree) sc = SkyCoord(newlon, newlat, unit='degree', frame=origin.frame.name) new_origin = origin.transform_to(new_frame) new_sc = sc.transform_to(new_frame) return new_origin.position_angle(new_sc) `````` If you look at other `SkyCoord` convenience functions (`match_to_catalog_*`, `separation*`, `to_pixel`), none of those depend on the frames used by the `SkyCoord` or the other object(s) (with the exception of `to_pixel`'s `wcs`). I don't know any other use for a `skycoord_from_position_angle` or `transform_then_do` besides one possible implementation of this, either. I did move away from this approach in order to be sure I remained consistent with previous code. Given that this will be needed whenever proper motions get handled and for @jwoillez's ideas with generalized frames, perhaps this could be tagged as a wishlist item for `astropy.wcs.utils`?
referenced this issue Dec 8, 2014

Member

### bsipocz commented Mar 27, 2019

 @eteq @adrn - isn't this one being addressed over the years by e.g. http://docs.astropy.org/en/stable/coordinates/matchsep.html#offsets ?