Skip to content

Add short worked examples to demo intended usage for each built-in coordinate frame #11701

Open

Description

@Phillip-M-Feldman

I am attempting to simulate visibility of a low-Earth-orbit satellite from a fixed ground station. The satellite elevation angles are all coming out around -17 degrees. This is not believable. As the satellite travels around the earth, the elevation angles should vary widely, and the satellite should at least occasionally be visible to the ground station (positive elevation angle). I'm assuming that I'm doing something wrong, but it is possible that there is a documentation issue here. My code follows.

# This script calculates visibility of a satellite to a single ground station.

# Section 1: Imports and constants.

from numpy import arange, empty, pi
from pdb import set_trace

import cartopy.crs as ccrs
from cartopy.feature.nightshade import Nightshade

from astropy import coordinates, time, units as u
from astropy.coordinates import AltAz, CartesianRepresentation, EarthLocation

from poliastro.bodies import Earth
from poliastro.twobody import Orbit, propagation
from poliastro.frames.fixed import ITRS
from poliastro.frames.equatorial import GCRS

RAD2DEG= 180 / pi


# Section 2: Simulate orbit.

a  = 7578.1 * u.km  # semi-major axis
ecc= 0      * u.one # eccentricity
inc= 63.4   * u.deg # inclination of the orbit plane
raan= 50    * u.deg # right ascension of the ascending node
argp= 270   * u.deg # argument of perigee
nu  = 0     * u.deg # true anomaly at epoch

T_sim_hrs= 24
T_step_min= 3

epoch= time.Time("2021-05-05 10:43")
orbit= Orbit.from_classical(Earth, a, ecc, inc, raan, argp, nu, epoch=epoch)

# Create time steps covering the duration of the simulation:
times= arange(0, 60*T_sim_hrs+1, T_step_min) * u.min

xyz= propagation.propagate(
  orbit,
  time.TimeDelta(times),
  method=propagation.cowell,
  rtol=1e-10,
)

epoch_times = epoch + times

# Calculate Earth-fixed (non-inertial) coordinates `itrs_xyz`:
gcrs_xyz = GCRS(xyz, obstime=epoch_times, representation_type=CartesianRepresentation)
itrs_xyz = gcrs_xyz.transform_to(ITRS(obstime=epoch_times))

# Convert satellite positions from Cartesian to spherical coordinates (radians and km):
sat_lat_lon= coordinates.cartesian_to_spherical(itrs_xyz.x, itrs_xyz.y, itrs_xyz.z)


# Section 3: Calculate satellite elevation angles.

# Reference: https://learn.astropy.org/rst-tutorials/Coordinates-Transform.html#transform-frames-to-get-to-altitude-azimuth-altaz

ground_station= EarthLocation.from_geodetic(
  lat   = 30 * u.deg,
  lon   = 30 * u.deg,
  height= 0  * u.km)

elevation= empty(len(sat_lat_lon[0]))

for i in range(len(sat_lat_lon[0])):
   satellite  = EarthLocation.from_geodetic(
     lat   =sat_lat_lon[1][i],
     lon   =sat_lat_lon[2][i],
     height=sat_lat_lon[0][i])

   # In the following call to `transform_to`, it is unclear why time is needed as an input.
   result= satellite.get_itrs().transform_to(AltAz(obstime=time.Time('J2010'),
     location=ground_station))

   elevation[i]= result.alt.value

set_trace()

Activity

  1. pllim commented on May 5, 2021

    @pllim
    Member

    Are you sure this is not an issue for poliastro? cc @astrojuanlu

  2. astrojuanlu commented on May 5, 2021

    @astrojuanlu
    Member

    Thanks @pllim for the ping. @Phillip-M-Feldman this is a super requested use case, I'll have a deeper look later this week. We need to isolate the problem - I trust Astropy more than poliastro :)

  3. Phillip-M-Feldman commented on May 5, 2021

    @Phillip-M-Feldman
    Author
  4. ayshih commented on May 5, 2021

    @ayshih
    Contributor

    First off, your transformation code can be much simpler. You should be able to go from gcrs_xyz straight to AltAz, e.g.:

    gcrs_xyz.transform_to(AltAz(obstime=epoch_times, location=ground_station))

    Your direct issue is that, in your later code, you are creating your AltAz frame with the fixed obstime=time.Time('J2010'), which is different from the default obstime=time.Time('J2000') that comes out of satellite.get_itrs(). That shift in obstime is why all of your satellite positions are being transformed incorrectly.

  5. mkbrewer commented on May 5, 2021

    @mkbrewer
    Contributor

    That's what I thought also. I found this attempted transformation quite curious. I also noticed that in:

       satellite  = EarthLocation.from_geodetic(
         lat   =sat_lat_lon[1][i],
         lon   =sat_lat_lon[2][i],
         height=sat_lat_lon[0][i])
    

    The height here is wrt to the WGS84 ellipsoid, not the geocenter.

  6. mkbrewer commented on May 5, 2021

    @mkbrewer
    Contributor

    I still can't figure out why all the elevations would come out the same. It seems to me that you'd have an array of positions that circle the Earth several times at an altitude of 7578 km above the ellipsoid and then are asking how these would be viewed from a particular location on Earth at 0 hours UT on January 1st, 2010, so the result should be a variety of elevations.

  7. ayshih commented on May 5, 2021

    @ayshih
    Contributor

    I imagine the reason is that the barycentric location of Earth is different between J2000.0 and J2010.0 (by ~900 Mm), so you're looking at an array of positions that are much farther away from Earth than their breadth.

  8. mkbrewer commented on May 5, 2021

    @mkbrewer
    Contributor

    I should think that the transformation to AltAz would handle the time difference through a CIRS->CIRS transform that goes back to the ICRS to take into account the difference in the Earth's position wrt the SSB.

  9. mkbrewer commented on May 5, 2021

    @mkbrewer
    Contributor

    Oh, it might be that from_geodetic() is expecting degrees and is given radians. This would result in all of the locations being within 6.28 degrees of each other in longitude.

  10. ayshih commented on May 6, 2021

    @ayshih
    Contributor

    I should think that the transformation to AltAz would handle the time difference through a CIRS->CIRS transform that goes back to the ICRS to take into account the difference in the Earth's position wrt the SSB.

    It doesn't null out the difference in Earth's position. That is, satellite.get_itrs() defaults to an obstime of J2000.0, so corresponds to the inertial position of the satellite in the vicinity of Earth at J2000.0, but you're calculating the vector to that from an observer based on Earth at J2010.0. Here's some code to illustrate, using just ground_station as defined above:

    >>> ground_station.get_itrs()
    <ITRS Coordinate (obstime=J2000.000): (x, y, z) in km
        (4787.61068827, 2764.12831965, 3170.37373538)>
    
    >>> ground_station.get_itrs().transform_to(AltAz(obstime='J2010', location=ground_station))
    <AltAz Coordinate (obstime=J2010.000, location=(4787.61068827, 2764.12831965, 3170.37373538) km, pressure=0.0 hPa, tem
    perature=0.0 deg_C, relative_humidity=0.0, obswl=1.0 micron): (az, alt, distance) in (deg, deg, km)
        (99.53622684, -17.73576362, 1113258.31359654)>

    Note the elevation of ~-17 degrees, and the distance of over a million kilometers.

  11. Phillip-M-Feldman commented on May 6, 2021

    @Phillip-M-Feldman
    Author
  12. pllim commented on May 6, 2021

    @pllim
    Member

    Is the issue resolved? If so, please close it. Thanks, everyone!

  13. mkbrewer commented on May 6, 2021

    @mkbrewer
    Contributor

    This is quite an interesting discussion. There may be another issue involved here. I will look into it.

  14. mkbrewer commented on May 6, 2021

    @mkbrewer
    Contributor

    OK. I understand this now. While, intuitively, the relative positions of two locations in the ITRS have no time dependence, that is not how it works in Astropy. Positions in Astropy are ultimately positions wrt the solar system barycenter at a given point in time. Therefore, specifying an ITRS position at one time and then observing it at another time, will, in fact, include the change in the position of the Earth wrt the SSB. As I suspected, there are a couple of other issues here.

    As far as I can see, the output of propagation.propagate() is an astrometric position wrt the geocenter rather than a true geocentric GCRS position. The later includes aberration applied to the vector from the geocenter to the target body. The former does not. Therefore, just doing the transform from GCRS to AltAz as suggested will incorrectly remove aberration from the geocentric position vector before adding it to the topocentric position vector from the ground station to the target body.

    Also, the target body is moving rather fast, so neglecting the light time delay may result in a significant error in the observed position. Unfortunately, Astropy does not yet have an astrometric frame, so the way to do this rigorously is to calculate the time delayed ICRS position of the satellite at each time of observation and then transform from ICRS to AltAz at the location of the ground station.

    This can be done by calling ground_station.get_gcrs_posvel(obstime=epoch_times) to get the astrometric position of the observer wrt the geocenter. Then for each time of observation, take the difference in the two positions and calculate the distance from the ground station to the satellite, iteratively retard the satellite orbit by the corresponding light time, and finally add the position of the Earth wrt the SSB at each time of observation to the time delayed astrometric position of the satellite.

  15. mhvk commented on May 7, 2021

    @mhvk
    Contributor

    @mkbrewer - it sounds like the above is another reason to actually ensure astropy has an astrometric frame. Since this issue should be closed (I think), would you mind transferring your text to the larger discussion so that we do not lose track of it?

  16. mkbrewer commented on May 7, 2021

    @mkbrewer
    Contributor

    Done.

  17. mhvk commented on May 7, 2021

    @mhvk
    Contributor

    @Phillip-M-Feldman - While there does not seem to be a true bug, it clearly is sufficiently complicated that it is not always clear how to handle things. Is there a place in the documentation where a bit of extra test might have helped you? Was there a specific place you looked/searched for?

  18. Phillip-M-Feldman commented on May 7, 2021

    @Phillip-M-Feldman
    Author
  19. ayshih commented on May 7, 2021

    @ayshih
    Contributor

    "test" --> "text"

  20. astrojuanlu commented on May 8, 2021

    @astrojuanlu
    Member

    It would be great if there were some documentation oriented toward people who use both astropy and poliastro.

    I wonder where such documentation should be, though. Not against adding stuff specific to artifical satellite tracking to the Astropy docs, but perhaps poliastro would be a better place, given that it also has groundtrack plotting and orbital element handling. What are people's thoughts on this?

  21. mhvk commented on May 8, 2021

    @mhvk
    Contributor

    @astrojuanlu - I agree that poliastro might make more sense in this example.

    For astropy, what I think could be really useful a set of simpler examples, aimed at specific coordinate frames. For instance, while working on the new HADec alternative to AltAz, I realized there were no simple examples of that (there is an observation planning page).

  22. removed their assignment
    on Feb 1, 2023
  23. changed the title [-]elevation angles are not believable[/-] [+]Add short worked examples to demo intended usage for each built-in coordinate frame[/+] on Sep 22, 2026
  24. adrn commented on Sep 22, 2026

    @adrn
    Member

    The original issue raised here is not a bug, but the final discussion points out that it would be good to have short, self-contained examples for all coordinate frames that demonstrate how they should be used for specific use cases.

    One other note relevant to the above: in the meantime, poliastro is no longer maintained and has been archived.

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