Skip to content

Cannot directly transform output from GCRS to AltAz / need ITRS location? #10996

Open

Description

@mhvk

EDIT: put a milestone of 4.3 for the feature request part of this issue, which is to normalize they arguments used to indicate an observer location.
EDIT 2: update to include more recent behaviour (#11069)

Following discussion in #10994, I thought the example below should work, where I use get_body() to get a SkyCoord with a GCRS position for Mars at a given time and location, and try to transform it to AltAz just using body.altaz.

However, it fails, because the temporary AltAz frame that was created does not have a .location attribute. This is unexpected, since the SkyCoord made by get_body does have that information - it is just stored in attributes with a different name, obsgeoloc and obsgeovel (which are CartesianRepresentation instead of EarthLocation for .location). Indeed, if I explicitly set my body.location to the relevant value, body.altaz does work.

So I guess one question is whether we can just translate between these attributes. In principle, the simplest route might be to just let AltAz understand obsgeopos as an alias for location (and maybe eventually deprecate AltAz.location in favour of it).

But perhaps a larger question is why AltAz and, e.g., GCRS are using different attributes in the first place for storing the observer location.

I should add that I'm not 100% sure either is ideal - for general use, I'd want to have the velocity stored on the position as a derivative (i.e., at least obsgeovel as a .derivatives['s'] on obsgeopos -- of course, the derivatives did not exist when obsgeovel was first introduced!). Also, it would seem better to have something that is explicit in what frame the position and velocity are defined. I think the information needed matches exactly what would be an ITRS or GCRS frame instance. So, perhaps one should have a single attribute that replaces location, obsgeoloc and obsgeovel? (and deprecate those three?) Reuse location? New obs_itrs?

cc @StuartLittlefair, @mkbrewer, @eteq, @adrn, @astrojuanlu

from astropy.coordinates import get_body, ITRS
from astropy.time import Time
from astropy import units as u

body = get_body('mars', Time(['J2010'], location=(10.*u.deg, 20*u.deg)))
# The following fails:
body.altaz
AttributeError                            Traceback (most recent call last)
<ipython-input-4-d1487e3dca10> in <module>
----> 1 body.altaz

~/data/mhvk/packages/astropy/astropy/coordinates/sky_coordinate.py in __getattr__(self, attr)
    784             frame_cls = frame_transform_graph.lookup_name(attr)
    785             if frame_cls is not None and self.frame.is_transformable_to(frame_cls):
--> 786                 return self.transform_to(attr)
    787 
    788         # Fail

~/data/mhvk/packages/astropy/astropy/coordinates/sky_coordinate.py in transform_to(self, frame, merge_attributes)
    617         # Do the transformation, returning a coordinate frame of the desired
    618         # final type (not generic).
--> 619         new_coord = trans(self.frame, generic_frame)
    620 
    621         # Finally make the new SkyCoord object from the `new_coord` and

~/data/mhvk/packages/astropy/astropy/coordinates/transformations.py in __call__(self, fromcoord, toframe)
   1420 
   1421             curr_toframe = t.tosys(**frattrs)
-> 1422             curr_coord = t(curr_coord, curr_toframe)
   1423 
   1424         # this is safe even in the case where self.transforms is empty, because

~/data/mhvk/packages/astropy/astropy/coordinates/transformations.py in __call__(self, fromcoord, toframe)
   1023             return reprwithoutdiff.realize_frame(reprwithdiff)
   1024         else:
-> 1025             return supcall(fromcoord, toframe)
   1026 
   1027 

~/data/mhvk/packages/astropy/astropy/coordinates/builtin_frames/cirs_observed_transforms.py in cirs_to_altaz(cirs_coo, altaz_frame)
     39 
     40     # first set up the astrometry context for CIRS<->AltAz
---> 41     astrom = erfa_astrom.get().apio(altaz_frame)
     42     az, zen, _, _, _ = erfa.atioq(cirs_ra, cirs_dec, astrom)
     43 

~/data/mhvk/packages/astropy/astropy/coordinates/erfa_astrom.py in apio(frame_or_coord)
    109             For this function, an AltAz frame is expected.
    110         '''
--> 111         lon, lat, height = frame_or_coord.location.to_geodetic('WGS84')
    112         jd1_tt, jd2_tt = get_jd12(frame_or_coord.obstime, 'tt')
    113 

AttributeError: 'NoneType' object has no attribute 'to_geodetic'

# but this works
body.location = ITRS(body.obsgeoloc).earth_location
body.altaz

<SkyCoord (AltAz: obstime=['J2010.000'], location=[(-2061640.62248365, 5629477.088513, 2169697.89603318)] m, pressure=0.0 hPa, temperature=0.0 deg_C, relative_humidity=0.0, obswl=1.0 micron): (az, alt, distance) in (deg, deg, m)
    [(281.60515631, 26.39730811, 1.10516762e+11)]>

Activity

  1. StuartLittlefair commented on Nov 6, 2020

    @StuartLittlefair
    Contributor

    This has been a particular pain point for me recently, as a result of implementing the TETE frame and geocentric CIRS. My thoughts on it were this:

    1. Generality - specifying an observer location with lat, lon, height is less general. It assumes an Earth-fixed observer. It makes sense for AltAz perhaps, but not for the other frames. I'd be against using location as the observer information on all frames.

    2. Efficiency - for ease of use with the erfa transformations, there's no preferred way of storing an observer location. Some routines - e.g apco, apio want the latitude, longitude and height; others want the observatory's geocentric position and velocity (apcs). At the moment, observer locations are usually coded in the frame in the efficient way - i.e the frame stores in the information in the form needed by the erfa transforms.

    3. Consistency - like SkyField, where all vectors are oriented w.r.t BCRS it made some sense to me to store observatory locations on all topocentric frames in the same coordinate system, and GCRS makes sense for almost all use cases. This also means one can easily check if GCRS/CIRS/TETE frames share the same observer. There's an argument to rename these attributes to gcrs_obsloc or similar I guess.

    There are some disadvantages to this - i.e in #10994 I have to convert AltAz.location into GCRS pos/vel to convert the CIRS coordinate into the correct topocentric CIRS frame, which is slow.

    Overall, I like the way we store the observer location now, but I can see the merit in the transformation machinery being able to infer the missing attributes if the information is present on the SkyCoord to do so.

  2. mhvk commented on Nov 6, 2020

    @mhvk
    ContributorAuthor

    @StuartLittlefair - it does seem the AltAz.location always is used as in locitrs = altaz_frame.location.get_itrs(obstime), i.e., ITRS, but the others assume GCRS. It might actually be easiest if the location is simply a frame instance of any kind, and gets transformed as needed.

    But more short-term, just hacking in the equivalency inside SkyCoord.transform_to may be easiest...

  3. StuartLittlefair commented on Nov 6, 2020

    @StuartLittlefair
    Contributor

    You've just blown my mind. Could that work? Frame instances as attributes of frames?

  4. ayshih commented on Nov 6, 2020

    @ayshih
    Contributor

    I'll note that SunPy already makes broad use of CoordinateAttribute for using frame instances to specify observer locations for our observer-based frames. (Technically, we subclass that frame-attribute class, and in our case, the frame instance is automatically transformed to our HeliographicStonyhurst frame.)

  5. mhvk commented on Nov 6, 2020

    @mhvk
    ContributorAuthor

    And we also use CoordinateAttribute internally in SkyOffsetFrame - I guess one just has to be sure that the frame in the attribute does not refer to its parent (or at least does not need a transformation of its parent that needs itself).

  6. StuartLittlefair commented on Nov 7, 2020

    @StuartLittlefair
    Contributor

    @mkbrewer has suggestions about how to tweak the transforms if we used location for all these frames. See #10994 (comment).

    This does have some advantages, it's certainly the easiest for the user to understand and perhaps could be efficient. My main concern - which could be a distaster for SunPy - is that it is only useful for a terrestrial observer.

  7. mkbrewer commented on Nov 7, 2020

    @mkbrewer
    Contributor

    Do CIRS, TETE and AltAz have an application for a non-terrestrial observer? It seems to me that earth rotation, precession and nutation wouldn't be applicable there. If a non-terrestrial observer needs to use an Earth centered coordinate system, GCRS would be more appropriate and all that's needed there is to specify the observer's position and velocity wrt the geocenter.

  8. mkbrewer commented on Nov 7, 2020

    @mkbrewer
    Contributor

    That said, GCRS position and velocity can easily be used to construct a pseudo EarthLocation by using the appropriate transforms, if desired. In fact, a method could easily be added to the EarthLocation class to do just that.

  9. mhvk commented on Nov 7, 2020

    @mhvk
    ContributorAuthor

    I like the idea for EarthLocation for CIRS, TETE, and AltAz, as well as a possible get_cirs_posvel() extension.

    Also agree that it would be super useful to think a bit more about observer locations away from Earth, in Earth orbit, at the Earth-Sun Lagrange points, etc. For those having an actual coordinate may end up easiest , i.e., to adapt the approach of SunPy. It may end up being easiest to just allow both approaches for some frames. @ayshih - what is the name you use for this observer coordinate? Could you perhaps point me to the code for a frame that uses it? (Sorry, I know I almost certainly can find this myself, but am hoping it will take you much less time that it would me!)

  10. added this to the v4.3 milestone on Nov 7, 2020
  11. ayshih commented on Nov 8, 2020

    @ayshih
    Contributor

    SunPy calls the frame attribute observer.

    To illustrate, here's the documentation for one of our observer-based frames: Heliocentric. In this frame, the Z-axis is aligned with the Sun-observer line. The observer frame-attribute type is actually subclassed from CoordinateAttribute to allow for string input that evaluates to a coordinate via get_body_barycentric().

    The transformation functions use that observer information as needed. For example, when transforming from our HeliographicStonyhurst to Heliocentric, it figures out the heliographic longitude/latitude of the observer from the Heliocentric frame, with care taken for any change in obstime, and then calculates the rotation matrix. (code link)

  12. StuartLittlefair commented on Nov 8, 2020

    @StuartLittlefair
    Contributor

    I’m thinking that, for the Earth-bound systems it is simplest to use location and GCRS would retain its current behaviour for flexibility.

    Is there any reason the coordinates of the observer in any given system (eg gcrs) can’t be a lazy property of the frame, which is calculated once and invalidated if the obstime changes on the frame (if that’s even possible)?

  13. mhvk commented on Nov 8, 2020

    @mhvk
    ContributorAuthor

    @StuartLittlefair - Agreed that that is simplest. In principle, I think lazy properties should be possible; the trick may perhaps be more of how to implement it for the frame transformations - those right now assume that all attributes can also be used to initialize the frame, which would suggest that we need to be able to pass in location, obsgeoloc and obsgeovel to all (and perhaps the can be "secondary attributes" of each other).

    Given this trickiness, an observer coordinate as is done in sunpy might actually be easier (where the attribute itself might well be able to take an EarthLocation as input; should check how sunpy does it...). But obviously that means deprecating the others...

  14. mkbrewer commented on Nov 16, 2020

    @mkbrewer
    Contributor

    @StuartLittlefair @mhvk It's been over a week here. Does that mean that you are stuck deciding among the various options or just that you are busy with other things?

  15. mhvk commented on Nov 16, 2020

    @mhvk
    ContributorAuthor

    Definitely busy with other things on my side. I think first decision point is whether for #10994 to pick an EarthLocation for CIRS rather than obsgeo{loc|vel} - but that is best discussed there!

  16. astrojuanlu commented on Nov 23, 2020

    @astrojuanlu
    Member

    At the moment (current master):

    • CIRS, TETE, and AltAz have location as a EarthLocationAttribute
    • GCRS and PrecessedGeocentric have obsgeo{loc,vel} as CartesianRepresentationAttribute.

    I agree with either (a) adding a new observer: CoordinateAttribute that deprecates location and obsgeo{loc,vel} or (b) reusing location as CoordinateAttribute and deprecating obsgeo{loc,vel}. I guess the implementation and the disruption would depend a lot on whether EarthLocation should be an ITRS subclass (#4261), but either way I support unifying the way we store "locations/observers".

  17. Cadair commented on Nov 23, 2020

    @Cadair
    Member

    Just going to chime in and say a +1 to observer having that be consistent with the astropy frames would be awesome.

  18. mkbrewer commented on Nov 24, 2020

    @mkbrewer
    Contributor

    On (b), the problem with that is that GCRS is the natural earth centered frame for an observer in earth orbit where the ITRS EarthLocation frame doesn't apply. This is why we limited location to the ground based CIRS, TETE and AltAz frames.

  19. mhvk commented on Nov 24, 2020

    @mhvk
    ContributorAuthor

    It may still be we need an ObserverCoord class that can be any frame, but internally tracks whether it is stuck to the Earth or not (i.e., if it is initialized with an EarthLocation). Although ideally it is extensible to other solar system bodies, in case one wants AltAz on Mars!

  20. mkbrewer commented on Nov 24, 2020

    @mkbrewer
    Contributor

    I think you'd need a MarsLocation subclass for that and the theory for that hasn't been invented yet. Not to mention precession and nutation.

  21. astrojuanlu commented on Nov 24, 2020

    @astrojuanlu
    Member

    (For reference frames of other planets, check out #5898)

  22. Cadair commented on Nov 25, 2020

    @Cadair
    Member

    I have long been thinking about an ObserverLocation class which could support moving the observer in time. I.e. you could initialise it with an EarthLocation and then evaluate it in ICRS at any given time. This could then be extended by specifying an orbit propagator for satellites etc. Core could implement this "orbit propagator" for known bodies were ERFA can evaulate the location of say Earth at any time.

    Basically I see the API as two components, an ObserverLocation class and some callable (probably class) which can transform the location to a concrete frame at a given time. You could then imagine subclasses of the callable which use poliastro or SPICE or just ERFA to do the calculations.

  23. mhvk commented on Nov 25, 2020

    @mhvk
    ContributorAuthor

    @Cadair - we have been separately thinking about propagators - that is obviously useful for targets as well! See #10404. Overall, I'm liking the sunpy approach of just using a regular coordinate for the observer more and more - it may just have to be able to propagate knowledge of whether it is stuck to the Earth. (Though perhaps getting a very small velocity in ITRS would be enough.)

  24. Cadair commented on Nov 25, 2020

    @Cadair
    Member

    sunpy has also done some hacks to retain the name of an observer once it is converted to a real coordinate (via a call to get body), so something along those lines would also be useful.

  25. pllim commented on Apr 28, 2021

    @pllim
  26. removed this from the v4.3 milestone on Apr 28, 2021
  27. mhvk commented on Apr 28, 2021

    @mhvk
    Author
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions