Astro::Coord::ECI::TLE::Iridium - Compute behavior of Iridium satellites


The following is a semi-brief script to calculate Iridium flares. You will need to substitute your own location where indicated.

 use Astro::SpaceTrack;
 use Astro::Coord::ECI;
 use Astro::Coord::ECI::TLE;
 use Astro::Coord::ECI::Utils qw{deg2rad rad2deg};

 # 1600 Pennsylvania Avenue, Washington DC, USA
 my $your_north_latitude_in_degrees = 38.898748;
 my $your_east_longitude_in_degrees = -77.037684;
 my $your_height_above_sea_level_in_meters = 16.68;
 # Create object representing the observers' location.
 # Note that the input to geodetic() is latitude north
 # and longitude west, in RADIANS, and height above sea
 # level in KILOMETERS.
 my $loc = Astro::Coord::ECI->geodetic (
    deg2rad ($your_north_latitude_in_degrees),
    deg2rad ($your_east_longitude_in_degrees),
 # Get all the Iridium data from CelesTrak; it is direct-
 # fetched, so no password is needed.
 my $st = Astro::SpaceTrack->new (direct => 1);
 my $data = $st->celestrak ('iridium');
 $data->is_success or die $data->status_line;
 # Parse the fetched data, yielding Iridium objects.
 my @sats = Astro::Coord::ECI::TLE->parse ($data->content);
 # We want flares for the next 2 days. In order to try to
 # duplicate as closely as
 # possible, we throw away daytime flares dimmer than -6,
 # and nighttime flares dimmer than -1. We also calculate
 # flares for spares, and assume night is any time the Sun
 # is below the horizon.
 my $start = time ();
 my $finish = $start + 2 * 86400;
 my @flares;
 my %mag_limit = (am => -1, day => -6, pm => -1);
 foreach my $irid (@sats) {
    $irid->can_flare (1) or next;
    $irid->set (twilight => 0);
    foreach my $flare ($irid->flare ($loc, $start, $finish)) {
        $flare->{magnitude} <= $mag_limit{$flare->{type}}
            and push @flares, $flare;
 print <<eod;
      Date/Time          Satellite        Elevation  Azimuth Magnitude
 foreach my $flare (sort {$a->{time} <=> $b->{time}} @flares) {
 # If we wanted to make use of the Iridium object that
 # produced the flare (e.g. to get apparant equatorial
 # coordinates) we would need to set the time first.
 ## $flare->{body}->universal ($flare->{time});
 #  The returned angles are in radians, so we need to
 #  convert back to degrees.
    printf "%s %-15s %9.1f %9.1f %5.1f\n",
        scalar localtime $flare->{time},
        $flare->{body}->get ('name'),
        rad2deg ($flare->{elevation}),
        rad2deg ($flare->{azimuth}),


As of early 2017 the flaring Iridium satellites are being taken out of service, to be replaced by non-flaring Iridium Next satellites. Once the flaring Iridium satellites are out of service, I plan to deprecate and remove all functionality relating to the acquisition of Iridium status.

I have not yet decided on the disposition of Astro::Coord::ECI::TLE::Iridium. My thought at the moment is that it will be moved to its own package rather than deleted, because the flare functionality may be of historical interest. By the same reasoning, the TLE parsing logic in Astro::Coord::ECI::TLE will remain able to rebless satellites as Iridium, but the canned status will be 'tumbling'.


This class is a subclass of Astro::Coord::ECI::TLE, representing original-design Iridium satellites. This class will probably not work for the Iridium Next satellites, which are being launched starting early 2017.

The Astro::Coord::ECI::TLE->parse() method makes use of built-in data to determine which satellites to rebless into this class, based on the object's NORAD SATCAT ID. This internal data can be modified using the Astro::Coord::ECI::TLE->status method to correct errors or for historical research. It is also possible to get an Iridium object by calling $tle->rebless (iridium => {status => $status}) directly.

What this subclass adds is the ability to generate information on Iridium flares (or glints, as they are also called). Members of this class are considered capable of generating flares based on their status, as follows:

 0 => in service
 1 => spare (may or may not flare)
 2 => failed - no predictable flares.

CelesTrak-style statuses ('+', 'S', and '-' respectively) are accepted on input. See Astro::SpaceTrack method iridium_status for a way to get current Iridium constellation status.


This class adds the following public methods:

$tle->after_reblessing (\%attribs);

This method supports reblessing into a subclass, with the argument representing attributes that the subclass may wish to set. It is called by rebless() and should not be called by the user.

At this level of the inheritance hierarchy, it sets the status of the object from the {status} key of the given hash. If this key is absent, the object is assumed capable of generating flares.

$tle->before_reblessing ()

This method supports reblessing into a subclass. It is intended to do any cleanup the old class needs before reblessing into the new class. It is called by rebless(), and should not be called by the user.

At this level of the inheritance hierarchy, it removes the status attribute.

$tle->can_flare ($spare);

This method returns true (in the Perl sense) if the object is capable of producing flares, and false otherwise. If the optional $spare argument is true, spares are considered capable of flaring, otherwise not. If $spare is 'all', then all objects are considered capable of flaring.

@flares = $tle->flare ($sta, $start, $end);

This method returns the list of flares produced by the given Iridium satellite at the given station between the given start time and the given end time. This list may be empty. If called in scalar context you get the number of flares.

All arguments are optional, with the defaults being

 $station = the 'station' attribute
 $start = time()
 $end = $start + 1 day

Each flare is represented by a reference to an anonymous hash, with elements as follows:

 angle => Mirror angle, radians
 appulse => information about the position of the Sun
   angle => distance from Sun to flare, radians
   body => reference to the Sun object
 area => Projected MMA area, square radians
 azimuth => Azimuth of flare, radians
 body => Reference to object producing flare
 center => information about the center of the flare
   body => location of the center of the flare
   magnitude => estimated magnitude at the center
 elevation => Elevation of flare, radians
 magnitude => Estimated magnitude
 mma => Flaring mma (0, 1, or 2)
 range => Range to flare, kilometers
 specular => True if specular reflection
 station => reference to the observer's location
 status => ''
 type => Type of flare (see notes)
 time => Time of flare
 virtual_image => Location of virtual image

Note that:

* The time of the object passed in the {body} element is not necessarily set to the time of the flare.

* The {center}{body} element contains an Astro::Coord::ECI object set to the location of the center of the flare at the given time. The center is defined as the intersection of the plane of the observer's horizon with the line from the virtual image of the illuminating body through the flaring satellite.

* The {mma} element indicates which Main Mission Antenna generated the flare. The antennae are numbered clockwise (looking down on the vehicle) from the front, so 0, 1, and 2 correspond to Heavens Above's 'Front', 'Right', and 'Left' respectively.

* The {specular} element is actually the limb darkening factor if applicable. Otherwise, it is 1 if the reflection is specular, and 0 if not.

* The {status} key is reserved for an explanation of why there is no flare. When the hash is generated by the flare() method, this key will always be false (in the Perl sense).

* The {type} element contains 'day' if the flare occurs between the beginning of twilight in the morning and the end of twilight in the evening, 'am' if the flare is after midnight but not during the day, and 'pm' if the flare is before midnight but not during the day.

* The {virtual_image} element is an Astro::Coord::ECI object representing the location of the virtual image of the illuminator at the time of the flare.

Why does this software produce different results than

The short answer is "I don't know, because I don't know how Heavens Above gets their answers."

In a little more detail, there appear to be several things going on:

First, there appears to be no standard reference for how to calculate the magnitude of a flare. This module calculates specular reflections as though the sky were opaque, and the flaring Main Mission Antenna were a window through to the virtual image of the Sun. Limb darkening is taken into account, as is atmospheric extinction. Non-specular flares are calculated by a fairly arbitrary equation whose coefficients were fitted to visual flare magnitude estimates collected by Ron Lee and made available on the Web by Randy John as part of his skysat web site at (missing; see ACKNOWLEDGMENTS). Atmospheric extinction is also taken into account for the non-specular flares.

Atmospheric extinction is calculated according to the article by Daniel W. Green in the July 1992 issue of "International Comet Quarterly", and available at Because Heavens Above does not display flares dimmer than a certain magnitude (-6 for day flares, and apparently 0 for night flares), it may not display a flare that this code predicts. I have no information how Heavens Above calculates magnitudes, but I find that this class gives estimates about a magnitude brighter than Heavens Above at the dim end of the scale.

Second, I suspect that the positions and velocities calculated by Astro::Coord::ECI::TLE differ slightly from those used by Heavens Above. I do not know this, because I do not know what positions Heavens Above uses, but there are slight differences among the results of all the orbital propagation models I have looked at. All I can say about the accuracy of Astro::Coord::ECI::TLE is that it duplicates the test data given in "Spacetrack Report Number Three". But small differences are important -- 0.1 degree at the satellite can make the difference between seeing and not seeing a flare, and smaller differences can affect the magnitude predictions, especially if they make the difference between predicting a specular or non-specular flare. Occasionally I find that I get very different results than Heavens Above, even when using orbital data published on that web site.

Third, Heavens Above issues predictions on satellites that my source says are spares. I have skipped the spares by default because I do not know that their attitudes are maintained to the requisite precision, though perhaps they would be, to demonstrate that the spares are functional. This software currently uses the Iridium status from CelesTrak (, since it represents one-stop shopping, and Dr. Kelso has expressed the intent to check with Iridium Satellite LLC monthly for status. Mike McCants' "Status of Iridium Payloads" at (no longer maintained) noted that flares may be unreliable for spares, so can_flare () returns false for them. If this is not what you want, call can_flare with a true value (e.g. can_flare( 1 )).

Fourth, the Heavens Above definition of 'daytime' differs from mine. Heavens Above does not document what their definition is, at least not that I have found. My definition of daytime includes twilight, which by default means the center of the Sun is less than 6 degrees below the horizon. I know that, using that definition, this software classifies some flares as daytime flares which Heavens Above classifies as nighttime flares. It appears to me that Heavens Above considers it night whenever the Sun is below the horizon.

Fifth, the orbital elements used to make the prediction can differ. I have occasionally seen Heavens Above using elements a day old, versus the ones available from Space Track, and seen this difference make a difference of six or eight seconds in the time of the flare.

Sixth, this method takes no account of the decrease in magnitude that would result from the Sun being extremely close to the horizon as seen from the flaring satellite. I do not know whether Heavens Above does this or not, but I have seen an instance where this code predicted a flare but Heavens Above did not, where the observed flare was much dimmer than this code predicted, and reddened. Subsequent calculations put the Sun 0.1 degrees above the horizon as seen from the satellite.

NOTE that the algorithm used to calculate flares does not work at latitudes beyond 85 degrees north or south, nor does it work for any location that is not fixed to the Earth's surface. This may be fixed in a future release. The chances of it being fixed in a future release will be enhanced if someone claims to actually need it. This someone will be invited to help test the new code.

NOTE also that as of version 0.002_01 of this class, the 'backdate' attribute determines whether a set of orbital elements can be used for computations of flares before the epoch of the elements. If 'backdate' is false and the start time passed to flare() is earlier than the epoch, the start time is silently moved forward to the epoch. The initial version of this functionality raised an exception if this adjustment placed the start time after the end time, but as of version 0.003_01 of this class, you simply get no flares if this happens.

$value = $tle->get ($name);

This method returns the value of the given attribute. Attributes other than 'status' are delegated to the parent.

$mag = $tle->magnitude( $station );

This override of the superclass' method method returns the magnitude of the body as seen from the given station at the body's currently-set time. If no $station is specified, the object's 'station' attribute is used. If that is not set, and exception is thrown.

This method calls the superclass' magnitude(), and returns undef if the superclass does. Otherwise it adds to the magnitude of the body itself the magnitude of any flare in progress, and returns the result.

@data = $tle->reflection ($station, $time)

This method returns a list of references to hashes containing the same data as returned for a flare, calculated for the given observer and time for all Main Mission Antennae. If $time is undef, the current time setting of the invocant is used. If $station is undef the current station attribute is used. Note the following differences from the flare() hash:

If the hash contains a 'status' key which is true (in the Perl sense), no reflection occurred, and the content of the key is a message saying why not. If the 'mma' key exists in addition to the 'status' key, the failure applies only to that MMA, and other MMAs may possibly generate a reflection. If the 'mma' key does not exist, then the satellite is either not illuminated or below the horizon for the given observer, and the @data list will contain only a single entry.

Other than (maybe) 'mma', no other keys should be assumed to exist if the 'status' key is true.

If called in scalar context, a reference to the \@data list is returned.

NOTE that prior to 0.061_01 the $time argument defaulted to the current time. This behavior was undocumented, and therefore I felt free to change it.

$tle->set ($name => $value ...)

This method sets the value of the given attribute (or attributes). Attributes other than 'status' are delegated to the parent.


This class adds the following attributes:

am (boolean)

If true, the flare() method returns flares that occur between midnight and morning twilight. If false, such flares are ignored.

The default is 1 (i.e. true).

day (boolean)

If true, the flare() method returns flares that occur between morning twilight and evening twilight. If false, such flares are ignored.

The default is 1 (i.e. true).

extinction (boolean)

If true, flare magnitude calculations will take atmospheric extinction into account. If false, they will not. The observer who wishes to compare forecast magnitudes to nearby stars may wish to set this to some value Perl considers false (e.g. undef).

The default is 1 (i.e. true).

intrinsic_magnitude (numeric or undef)

This attribute is inherited from the parent class, but unlike the parent (which defaults it to undef), this class defaults it to 7.0, which is the average intrinsic magnitude for an Iridium satellite.

max_mirror_angle (angle in radians)

This attribute is used in the flare calculation to screen passes for flare potential. The position of the satellite is calculated at intervals during a pass, and for each position the mirror angle (defined as the angle subtended at the satellite between the observer and the reflection of the Sun) is calculated. If no calculated mirror angle is less than the value of this attribute, no flare is predicted. If one of the mirror angles is less than this, a flare is predicted and its maximum magnitude calculated.

The default value is equivalent to 10 degrees. This is fine for fairly bright flares, but if you are looking for really dim ones (relative to the maximum brightness given the source of illumination) you want to increase this. The cost of increasing it is, of course, increased computation. The maximum useful value is the equivalent of 180 degrees, but this is not enforced. If you have a particular angle in mind, you will want to set this to about 10 degrees more, because of the way it is used in the flare algorithm.

pm (boolean)

If true, the flare() method returns flares that occur between evening twilight and midnight. If false, such flares are ignored.

The default is 1 (i.e. true).

status (integer)

This attribute determines whether the Iridium satellite is considered able to produce predictable flares. The possible values are:

 0 => in service;
 1 => spare, or maneuvering;
 2 => out of service, tumbling, et cetera;
 3 => decayed.

By default, the can_flare() method returns true only if the status is 0. But if given a true argument (e.g. can_flare(1)) it will also return true if the status is 1.

When setting this attribute, both T. S. Kelso and Mike McCants style strings are accepted. That is:

 '+' or '' will be considered 0;
 'S' or '?' will be considered 1;
 anything else will be considered 2.

Technically, the default is 0. But if the object is manufactured by Astro::Coord::ECI::TLE->parse(), the status will be set based on the internal status table in Astro::Coord::ECI::TLE.

Note that Mike McCants' Iridium statuses are no longer maintained, so his status codes ('' and '?') are being put through a deprecation cycle. In 6 months they will warn on the first use; 6 months after that they will warn on every use, and 6 months after that they will become fatal.

zone (number or string)

This attribute is used to determine whether a flare took place in the am or pm. It can be set to a number of possible values:

* undef causes the computation to be done in the local zone, whatever that is.

* A numeric value is interpreted as an offset in hours east of Greenwich; hours west are negative.

* Anything else is interpreted as a zone name. If DateTime is available, it is used to convert the time to the named zone, throwing an exception if the zone name is not recognized.

Otherwise, $ENV{TZ} is set to the zone, and then localtime() is called. Whether this has the desired effect depends on your operating system.

Yes, this is a bit of a crock, but I realized (after putting out 0.036, with the Iridium flare tests that anyone can run) that I needed to do something to get a user's-zone-independent test.

The default is undef.


The author wishes to acknowledge the following people, without whose work this module would never have existed:

Ron Lee and the members of his team who collected Iridium magnitude data.

Randy John, the author of SKYSAT (, whose Turbo Pascal implementation of the geometry calculation (at provided the basic mechanism for my own geometry calculation, and who made Ron Lee's data available on the SKYSAT web site. Unfortunately Comcast has long since stopped providing personal pages, and I have found nothing more recent. The most-recent capture of this by the Internet Archive's Wayback Machine is September 19 2015.

The contributors to the Visual Satellite Observer's Home Page (, particularly the Iridium Flares page (, which provided the background for the entire Iridium flare effort.


Support is by the author. Please file bug reports at,, or in electronic mail to the author.


Thomas R. Wyant, III (wyant at cpan dot org)


Copyright (C) 2005-2022 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.