++ed by:

4 PAUSE users

Author image Tom Wyant
and 1 contributors


Astro::Coord::ECI::OVERVIEW - Overview of Astro::Coord::ECI and friends


This package was created in order to be able to forecast satellite visibility. This is not quite rocket science, but like many endeavors the devil is in the details, which in part accounts for the complexity of the package.


Any system to forecast the visibility of an object in the sky needs to be told a few things: where you are, where the object in the sky is, and (maybe) what source of illumination is being used. This package provides classes for all these. Some of the objects (e.g. Astro::Coord::ECI::Sun) are pretty much self-sufficient. Others (e.g. Astro::Coord::ECI::TLE) need to be initialized before they can be used.

Typically a calculation is done by initializing an object representing the station (i.e. you), another representing the orbiting body (i.e. the satellite), then calling a method on one of them and passing it the other one.

For example, to calculate visibility of the International Space Station from your front yard you would initialize an Astro::Coord::ECI object with the latitude (in radians), longitude (in radians), and height above sea level (in kilometers). You would also initialize an Astro::Coord::ECI::TLE object with TLE data for the space station. Then you would call method pass() on the space station object, giving it the object that represents your location, and the times (as Perl times) you are interested in. The result would be an array of hashes with the visibility data.

This is actually a worst case. The position of a satellite is not dependent on you, so if all you need to know is it's position over the globe, all you need to do is set its object's time. This will cause the model to run. Then you call the method that gives the position in the coordinates you want. For absolute coordinates (e.g. geodetic latitude and longitude) no other objects are involved. For relative coordinates (e.g. azimuth and elevation) you need to make use of the object the coordinates are relative to.

This documentation assumes that you are interested in satellites, but there are a couple supporting classes which can equally well be used in their own right: Astro::Coord::ECI::Sun, which represents the Sun, and Astro::Coord::ECI::Moon.

So far this explanation has been in terms of a single Astro::Coord::ECI::TLE object. But there are cases (e.g. a Space Shuttle launch) where a single Astro::Coord::ECI::TLE object can not represent a single physical object over a conveniently-long period of time (say, a week, starting some time before the launch). In this case, the multiple Astro::Coord::ECI::TLE objects can be aggregated into a single Astro::Coord::ECI::TLE::Set object, using that class' aggregate() method. This method will assort its arguments into the appropriate number of containers, and return one object per OID. By default this will be an Astro::Coord::ECI::TLE object if there is only one instance of the given OID in the input, or an Astro::Coord::ECI::TLE::Set object if there is more than one.

The Astro::Coord::ECI::TLE::Set object can be used anywhere an Astro::Coord::ECI::TLE object can. There is a slight performance penalty, but on the other hand you get better results, or in extreme cases you actually get results where you would not with a single TLE.


The example code in the SYNOPSIS sections of the various modules is (or at least should be) working Perl. If you find that it is not working perl, please submit a bug report.

Other examples are available in the eg directory of the distribution.

Both the module documentation and the eg directory are available online at https://metacpan.org/release/Astro-satpass. For the eg directory you will need to follow the [browse] link near the top of the page.


Bugs should be reported to https://github.com/trwyant/perl-Astro-Coord-ECI/issues/, or to the author via electronic mail at wyant at cpan dot org.


Different units of measure are usual in different applications. Since these modules span the disciplines of geodesy, aeronautics, and astronomy, a somewhat Procrustean approach has been necessary to provide consistency of interface and try to preserve the sanity of the author.


All angles, both input and output, are in radians. The Astro::Coord::ECI::Utils module exports functions deg2rad() and rad2deg() to convert degrees to and from radians. rad2dms() and rad2hms() are provided for radians to degrees, minutes, and seconds or for hours, minutes, and seconds of right ascension, but the user is on his or her own for input conversion of these to radians.

The expected range of an angle depends on what is customary for the quantity being measured. Values outside the customary range will generate warnings.


All distances, both input and output, are in kilometers.


All times are seconds since the system's epoch. That is, the kind of time returned by time().


Forecasting satellite visibility is an activity on the border between aeronautics and astronomy. It needs technical words, and borrows from both these disciplines, tending to prefer the aeronautical term when there is conflict. There is a fairly exhaustive TERMINOLOGY AND CONVENTIONS section in the documentation for Astro::Coord::ECI, but a few key terms will be covered here:


Short for 'orbiting body', this is the satellite, spent booster, dropped hammer, or other orbiting item that you are trying to observe. Bodies of all sorts are identified by an OID. Several models have been published to predict the position of such an item at a given body.

Orbiting bodies are represented in this package by an Astro::Coord::ECI::TLE object. Before this object can be used, it needs to be initialized with data for the specific satellite of interest. The standard representation of this data is called TLE, and is explained below.


In general and in astronomical calculations, the epoch is a date and time to which time-dependent calculations are referred. For example, the position of a star or planet may be given in epoch J2000, meaning the coordinates for Julian year 2000.

But in orbital calculations it can also be used as a proxy for the 'as-of' date of the orbital data. This is important for TLE data because this data has a limited 'shelf life', and the farther from the epoch you are the worse the predictions are. In extreme cases the model itself fails because it cannot handle the type of orbit predicted.

Typically the 'shelf life' of a TLE is a couple weeks for casual use, though data for satellites in near-Earth orbits (defined as a period of less than two and a half hours) are usually updated every couple days. In extreme cases (e.g. the first arc of a Space Shuttle flight) the model may fail within a couple days of the epoch. See the TLE documentation for more information.


Short for 'Object ID', this is a unique number assigned to an object by NORAD or its successors when the orbiting body is detected. The OID may also be called the 'Satellite ID', the 'Satellite Number', or simply the 'ID'.


Short for 'observing station', this is the location of the observer. This package only supports observers on the surface of the Earth, so the observer will be represented by an Astro::Coord::ECI object.

This object will need to be initialized by calling its geodetic() method, passing the observer's latitude and longitude in radians and height above sea level in kilometers. Latitude is negative south of the Equator, and longitude negative west of Greenwich, England. The Astro::Coord::ECI::Utils packages exports deg2rad() to do the angle conversion.


Short for 'Two line elements', this is the standard representation of the data used to initialize any of the usual models for the positions of orbiting bodies. It is basically two lines of text, and owes a lot to the days when computers were fed data by making holes in pieces of cardboard.

A common variant of this format is sometimes called 'NASA TLE'. The format is the same, but the two lines of model data are preceded by a line containing the common name of the body being modeled.

With the introduction of the REST (or Version 2) interface to the Space Track web site, a third representation of TLE data, as JSON, has become available.

Given a chunk of TLE data, the Astro::Coord::ECI::TLE parse() method can be used to turn it into Astro::Coord::ECI::TLE objects -- or Astro::Coord::ECI::TLE::Iridium objects if you have installed that module from its own distribution. If your data are in JSON format, you will need the optional JSON module installed.

In practice, TLE data have an effective date, and a shelf life measured from that effective date. Unfortunately, the TLE data do not contain either of these.

NASA's Human Space Flight data, available from https://spaceflight.nasa.gov/realdata/elements/, specify an effective date (see the Vector Time information on that web page), but this is not part of the TLE. The Astro::SpaceTrack package is capable of returning this data by jamming it onto the first line of a NASA TLE, and this package's parse() method is capable of populating the object from this information. If an effective date is needed but not available, the epoch is used.

For casual use, the 'shelf life' of a set of TLE data is a week or so. But this can vary based on circumstances. For example, if the data represent a coasting arc for a shuttle launch, the shelf life may be a matter of minutes. For something like the International Space Station, the shelf life is cut short when the station is boosted into a higher orbit. One would hope that the Human Space Flight data would take account of this, but in fact it appears not always to do so.

Most sources of data provide only a single current TLE for a given object. If you are processing historical data from Space Track, or if you are processing NASA Human Space Flight data, you may have multiple TLEs for a given object. In this case, you should use the Astro::Coord::ECI::TLE::Set aggregate() method to aggregate all the data for a given satellite into a single Astro::Coord::ECI::TLE::Set object. This object behaves pretty much like an Astro::Coord::ECI::TLE object, but when doing things like pass calculations, it selects the appropriate TLE data for a given time from its contents. The aggregate() method does not need for all its input to represent the same satellite; it returns an array of objects, with the input appropriately assigned. If there is only a single Astro::Coord::ECI::TLE object for a given OID, this object is not wrapped in a set, to avoid the overhead involved.

Even with all this, if you anticipate that you may use data far from its effective date, or that you may have bad data, you should call the Astro::Coord::ECI::TLE validate() method before doing pass calculations. Among other things, this gives the Astro::Coord::ECI::TLE::Set object a chance to groom its own data.

This package does not deal with the acquisition of TLE data. See "WHERE TO GET TLE DATA", below, for that.


The modules in this package are organized functionally for the most part, with utility functions separate. The following list names the modules, and briefly states their function.


The Astro::Coord::ECI module is the base for the inheritance tree. It provides position and time transformations to its subclasses, and doubles as the object used to represent a location fixed to the surface of the Earth.

For the latter use you probably would construct the object by calling the Astro::Coord::ECI geodetic() method, passing it the latitude and longitude in radians, and the height above sea level in kilometers. The Astro::Coord::ECI::Utils module exports a deg2rad() function to convert degrees to radians. Remember that latitude is negative south of the equator, and longitude is negative west of the Prime Meridian in Greenwich, England. Other prime meridians are not supported.


The Astro::Coord::ECI::Moon module is a subclass of Astro::Coord::ECI which represents the Moon. All you have to do to use it is to instantiate it (say, with my $moon = Astro::Coord::ECI::Moon->new()), and then set the time with something like $moon->universal($time), where the $time is a Perl time, such as is returned by the time() built-in. Then you can retrieve the position using one of the methods inherited from Astro::Coord::ECI.

You can pass an Astro::Coord::ECI::Moon object to the Astro::Coord::ECI::TLE pass() method, and it will report on close approaches to the Moon. How close depends on the setting of the Astro::Coord::ECI::TLE object's 'appulse' attribute. This defaults to 0, which means no appulse calculations are done.


The Astro::Coord::ECI::Star module is a subclass of Astro::Coord::ECI which represents a star. It is initialized by calling the position() method to set the star's position in right ascension and declination in radians, and optionally its distance and proper motion. After this, simply setting the time of the object (e.g. by $star->universal($time), $time being a Perl time) will cause its position to be calculated. The position can be retrieved using one of the methods inherited from Astro::Coord::ECI.

You can pass an Astro::Coord::ECI::Star object to the Astro::Coord::ECI::TLE pass() method, and it will report on close approaches to the star. How close depends on the setting of the Astro::Coord::ECI::TLE object's 'appulse' attribute. This defaults to 0, which means no appulse calculations are done.


The Astro::Coord::ECI::Sun module is a subclass of Astro::Coord::ECI which represents the Sun. All you have to do to use it is to instantiate it (say, with my $sun = Astro::Coord::ECI::Sun->new()), and then set the time with something like $sun->universal($time), where the $time is a Perl time, such as is returned by the time() built-in. Then you can retrieve the position using one of the methods inherited from Astro::Coord::ECI.

This object is implicitly used by the Astro::Coord::ECI::TLE pass() method to calculate whether the satellite is illuminated, and whether it is day, dusk, or night at the observing station.

You can pass an Astro::Coord::ECI::Sun object to the Astro::Coord::ECI::TLE pass() method, and it will report on close approaches to the Sun. How close depends on the setting of the Astro::Coord::ECI::TLE object's 'appulse' attribute. This defaults to 0, which means no appulse calculations are done.


The Astro::Coord::ECI::TLE module is a subclass of Astro::Coord::ECI which represents a satellite or other body in orbit around the Earth. This representation is in terms of one of a number of related computational models, defined in Space Track Report Number 3 and Revisiting Spacetrack Report #3, both of which are available at http://celestrak.com/NORAD/documentation/.

It is possible to construct an Astro::Coord::ECI::TLE object by setting the requisite attributes directly, but it is probably more convenient to do so using the Astro::Coord::ECI::TLE parse() static method. This method accepts TLE data, and returns an array of Astro::Coord::ECI::TLE objects parsed from the data.

The position of a satellite is computed by setting the time represented by the object. This is typically done using the Astro::Coord::ECI universal() method (inherited from Astro::Coord::ECI), by passing it a Perl time (i.e. the kind returned by the time() built-in). Once the position is computed, it can be retrieved by using any of the positional methods defined on Astro::Coord::ECI.

This module also offers the Astro::Coord::ECI::TLE pass() method, which calculates visible passes over an observer's location during a given time interval.

One of the complications of using this module is that the TLE data are only good for a limited amount of time around the epoch of the data. In most cases this just results in a loss of precision, but in extreme cases (such as the first predicted element of a Space Shuttle launch) use of the data by as little as a day or so before its epoch can cause the model computations to fail. For cases like this there is a 'backdate' attribute, which tells the Astro::Coord::ECI::TLE pass() method whether it should consider times before the epoch of the data. It defaults to true because that represents the general case. But you may wish to set it false (e.g. by something like $tle->set(backdate => 0);) if you are using the predicted data downloaded from the "NASA Human Space Flight" web site. You may also wish to call the Astro::Coord::ECI::TLE validate() method before making pass calculations if you suspect that your TLE data may not all be valid.


This module is a subclass of Astro::Coord::ECI::TLE representing an Iridium Classic satellite. The Astro::Coord::ECI::TLE parse() static method produces these automatically when fed TLE data representing Iridium satellites.

This module adds to the Astro::Coord::ECI::TLE functionality the ability to calculate Iridium Classic flares. The Astro::Coord::ECI::TLE::Iridium flare() method is passed the observers' location and the desired time range for the prediction. It returns a list of hashes representing the predicted flares. This prediction is modified by attributes giving limiting magnitudes for daytime and nighttime flares, and whether you want predictions for am (between midnight and sunrise), day (between sunrise and sunset), and/or pm (between sunset and midnight) flares.

This module was moved into its own distribution in mid-2018, due to the impending replacement of all Iridium Classic satellites with Iridium Next. The Iridium Next satellites are not expected to flare (at least not nearly as spectacularly as the Iridium Classic satellites), and have a different geometry anyway, so this class is useless for them.


This module is not a member of the Astro::Coord::ECI inheritance tree. It is a container for one or more Astro::Coord::ECI::TLE objects representing the same OID, and is usable pretty much anywhere an Astro::Coord::ECI::TLE object is.

The Astro::Coord::ECI::TLE::Set object exposes all the methods of its contained objects, but overrides some of them to work its magic. Astro::Coord::ECI::TLE::Set works on the principle that one of its contained objects is the current object, which is used to satisfy data requests. So the most important override is of the Astro::Coord::ECI universal() method, which selects as the current object the one best representing the given time. In this context, 'best' means the object having the most recent effective date (or epoch) before the given time. If there is no such object, the object having the earliest epoch is used.

There are a couple other small magics involved in Astro::Coord::ECI::TLE::Set:

* The can() method is overridden, so that an Astro::Coord::ECI::TLE::Set object appears to implement the methods of whatever class it was populated with.

* The set() method is overridden so that attributes characteristic of the OID (such as 'name', 'backdate', and so on) being represented are set on all contained objects, but attributes characteristic of a specific TLE data set are set only on the selected object.

* For select methods, such as the Astro::Coord::ECI::TLE pass() method, the Astro::Coord::ECI::TLE::Set object passes itself to to the method as the invocant, rather than passing the current object. This way such methods transparently switch TLE data sets whenever appropriate.


The Astro::Coord::ECI::Utils module is not a member of the Astro::Coord::ECI inheritance hierarchy, but is a container for all those utility subroutines that are not intrinsically object-oriented. These are generally conversion routines of some sort, such as the previously-mentioned deg2rad() subroutine and its inverse rad2deg(), and conversions among the various time representations used in the various models.


There are a number of places to get the TLE data on line. Some of these are accessible through the Astro::SpaceTrack Perl module (not included in this package, but available from CPAN), but you can always simply download the data and then read the file and pass its contents to the Astro::Coord::ECI::TLE static method parse().

Please note that anything specific said here about the functionality of Astro::SpaceTrack is not definitive. You should consult Astro::SpaceTrack for the latest.

Space Track

The 'most official' source of TLE data is https://www.space-track.org/, which requires you to register and use a username and password. Other sources may get their data from here, which means their data are hours to days older. Astro::SpaceTrack will retrieve data from this site, but of course you have to give it a username and password before it will do so.

As of about April 13 2011, you will need Astro::SpaceTrack version 0.052 or higher to access Space Track. On or about that date they changed from straight http to secure http (https:), and 0.052 is the version at which Astro::SpaceTrack gained this functionality.

As of about July 16 2013, you will need Astro::SpaceTrack version 0.054 or higher to access Space Track. On or about that date they will retire the version 1 interface, so you will need an Astro::SpaceTrack that adequately supports the new interface.


Dr. T. S. Kelso's http://celestrak.com/ is a good source for the more popular satellites. It redistributes data from "Space Track", but you can get Dr. Kelso's data without registering. Astro::SpaceTrack will retrieve data from this site, but by default is simply gets the OID from Dr. Kelso, and then retrieves the data from Space Track, so you will need a username and password. If you don't have them, you can configure Astro::SpaceTrack to work for this site without them.

NASA Human Space Flight

NASA makes International Space Station data available at https://spaceflight.nasa.gov/realdata/elements/, but the TLEs have to be dug out of the data. Astro::SpaceTrack will dig out the data for you, and you do not need a username or password. Unlike the other sources, these data include predictions of future TLEs.

Amateur Radio Satellite Corporation

The Amateur Radio Satellite Corporation (AMSAT) keeps orbital elements at https://www.amsat.org/keplerian-elements-resources/. They appear to get the data from Dr. Kelso. Astro::SpaceTrack will get this data for you, and you need no username or password.

Heavens Above

Heavens Above at https://www.heavens-above.com/ does visibility predictions, and will give you TLE data if you drill down far enough on an individual satellite. I do not know where they get their data. I have observed it to be a day or so behind Space Track, but they also carry data from classified satellites (which Space Track does not). You can create an account for yourself, but this is not necessary to use the site. Astro::SpaceTrack does not retrieve data from this site.


Because Heavens Above (https://www.heavens-above.com/) is a good source of local satellite visibility predictions, you may wish to try to duplicate its predictions as part of your process for qualifying this software. Part of the problem with doing this is that Heavens Above's prediction parameters are not documented. Some (like horizon) can be inferred from their predictions, others can not.

For the closest approach to Heavens Above's predictions, you should use TLE data of the same epoch that Heavens Above uses. Beyond this, the following settings seem to duplicate Heavens Above fairly well:

* Horizon - 10 degrees;

* Twilight - -3 degrees (passes), or -1.8 degrees (Iridium flares).

The edge_of_earths_shadow attribute is so new I have no firm idea what setting would best duplicate Heavens Above, but there is some evidence that, for satellite passes at least, -1 gives results more like those of Heavens Above than the default of 1.

Since Heavens Above only predicts one pass at a time, it may actually choose a different twilight for each satellite, based on the satellite's brightness. The same may be true for Iridium flares. If you are trying to validate this software against Heavens Above, you will want to make its settings a little more permissive, to make sure you pick up everything Heavens Above picks up.


This package was written with the intent of tracking artificial satellites. Modules that represent astronomical objects (in particular the Sun and Moon) were implemented using the fastest and least-accurate algorithm in Jean Meeus' book, because the stated accuracy of that algorithm was sufficient for my purposes, which were generally telling whether a satellite was illuminated.

I am not an expert in historical astronomical calculations, nor do I play one on television. I do not know the behavior of the algorithms used in this module when well away from the present, nor do I know how authoritative other resources are. But for the curious, I present comparisons of my results with resources that are available to me.

The modules in this package do calculations in terms of epoch time (i.e. the kind returned by time()). With the usual 0 being midnight January 1 1970 UTC, you will need a fairly modern Perl with 64-bit times to go back before about 1902.

If you go far enough back, there are problems converting between internal and external representations. The only documented restrictions on the built-ins gmtime() and localtime() are the ones on the size of the time representation, discussed above. Going the other way (from year/month/day to epoch) can be done with core module Time::Local, but once the year becomes less than 1000, it means something other than a literal Gregorian year. The DateTime module does not have this limitation, but you need to be aware that the DateTime::TimeZone modules representing Olson zones (at least the ones I investigated) represent time before standard time was adopted as local mean time in the zone's city. UTC does not have this problem, and I believe that offsets and the 'floating' "zone" do not either.

The other problem is that of what calendar is in use. Strictly speaking, the Julian calendar was in use before the Gregorian calendar was adopted, at various times ranging from October 15 1542 to April 4 1919, depending on where you are. If you want Julian-calendar dates, DateTime::Calendar::Christian will provide dates in the Julian calendar before the reform date (whichever one you choose to use) and Gregorian dates after. But you need to be a little careful with it, because it does not implement the full DateTime interface.

One of the resources for comparison data is CalSky (https://calsky.com/). This does astronomical calculations back to 2999 BC, but there are problems if you go too far back. In particular I got no Sunrise time for any location I tried before about 1508 BC, and only one phase of the Moon for the entire month of July 2999 BC. Times appear to be presented in the Julian calendar before 1543, but I have not investigated whether they convert on October 15, and I have not yet found documentation where they say what they are doing. I also have no idea how they extend time zones back before there were time zones, so I have asked for times in UTC.

Another resource is Stellarium (http://stellarium.org/, which is open source planetarium software. I could not figure out how to get things like equinox/solstice and Moon phases out of it, but rise times and locations can be obtained just by setting the time appropriately and then running it. Because resetting the time zone is fairly dodgy, I simply added five hours to my computed times in UT-5 and reported them. It does report that times are Gregorian or Julian when you set them. But instead of setting the year to (e.g.) 2999BC you have to set it to -2998. The calculations were done with Stellarium 0.15.0.


For the Sun I present calculations for the northern solstice in 1508 BC, that being the earliest one I could find that with sunrise data. Times are UTC on July 7 1508 BC in the Julian calendar, and sunrise data are as observed from the Royal Observatory in Greenwich England.

                          Solstice     Sunrise    Azimuth
 Astro::Coord::ECI::Sun   00:15:46     03:32:32   48.1
                 CalSky   21h53.8m (1)  3h33.0m   48.1
             Stellarium   16:36:57 (2) 03:35:16   48.5
1) The CalSky solstice was on the previous day, July 6. I reported the sunrise data for July 7 because CalSky did not report a Sunrise or sunset on July 6.
2) The Stellarium solstice was the moment at which the ecliptic longitude on the date was 90 degrees, and was also on the previous day (i.e. the July 6).


For the Moon I present calculations for the first quarter in July 2999 BC, which took place on July 22 Julian. Moon rise data are as observed from the Royal Observatory in Greenwich England.

                          First Quarter  Moon rise  Azimuth
 Astro::Coord::ECI::Moon  18:04:46       11:55:07   96.8
                  CalSky  13h13.0m       12h12m     99
              Stellarium  11:07:50 (1)   12:14:16   99.3
1) The Stellarium first quarter was the moment at which the phase angle was 90 degrees.


Thomas R. Wyant, III wyant@cpan.org


Copyright (C) 2009-2021 by Thomas R. Wyant, III

This program is free software; you can redistribute it and/or modify it under the same terms as Perl 5.10.0. For more details, see the full text of the licenses in the directory LICENSES.

This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.