# Calculating Which Satellite Passes are Visible

Using the Python library pyephem I am calculating passes for the ISS based on TLE data and my lat/long, but how can I tell which of the many passes it returns per day are visible?

I understand that satellites are only visible around sunrise or sunset, but is there any systematic way to calculate wether or not a pass is in the range of time when it could be visible?

• Don't forget about the visibility during the bright phases of the Moon. ;) – TildalWave Apr 23 '14 at 4:13
• I like this question. The code to do this would be a great addition to pyephem. – Stu Apr 23 '14 at 12:47

After doing some research I have found the answer to my problem on Celestrak. They have a great article about exactly this that I will summarize here, though if your interested in doing this you should read it here.

There are 3 main requirements for a satellite to be visible:

• The satellite must be above the observer's horizon.
• The sun must be below the observer's horizon enough to darken the sky.
• The satellite must be illuminated by the sun.


The first point we can ignore because by definition a pass calculated by any library or website has to be over the horizon for it to be a pass over the specified location.

This second point is crucial for what I am interested in. The sun must be at least -6˚ below the horizon so that it is dark enough that the light from the sun reflected on the satellite is bright enough relative to the sky to stand out, this is called the nautical twilight. So the sun must have set, but it also must not be below -18˚ otherwise not enough sunlight can get to the satellite for it to be visible. This is why you can only see satellites for a few hours around dawn or dusk.

The third point is that the object must not be eclipsed by the earth. If you are not using pyephem or another library that calculates this then I highly suggest you read their article which details how to calculate it. Since pyephem does calculate wether the object is eclipsed I will not get into the math here and simply say that this can be found using iss.eclipsed for example.

To tie this all together, using pyephem one can find the next pass and wether it's visible like this:

def seconds_between(d1, d2):
return abs((d2 - d1).seconds)

def datetime_from_time(tr):
year, month, day, hour, minute, second = tr.tuple()
dt = datetime.datetime(year, month, day, hour, minute, int(second))
return dt

def get_next_pass(lon, lat, alt, tle):

sat = ephem.readtle(str(tle[0]), str(tle[1]), str(tle[2]))

observer = ephem.Observer()
observer.lat = str(lat)
observer.long = str(lon)
observer.elevation = alt
observer.pressure = 0
observer.horizon = '-0:34'

now = datetime.datetime.utcnow()
observer.date = now

tr, azr, tt, altt, ts, azs = observer.next_pass(sat)

duration = int((ts - tr) *60*60*24)
rise_time = datetime_from_time(tr)
max_time = datetime_from_time(tt)
set_time = datetime_from_time(ts)

observer.date = max_time

sun = ephem.Sun()
sun.compute(observer)
sat.compute(observer)

sun_alt = degrees(sun.alt)

visible = False
if sat.eclipsed is False and -18 < degrees(sun_alt) < -6 :
visible = True

return {
"rise_time": timegm(rise_time.timetuple()),
"rise_azimuth": degrees(azr),
"max_time": timegm(max_time.timetuple()),
"max_alt": degrees(altt),
"set_time": timegm(set_time.timetuple()),
"set_azimuth": degrees(azs),
"elevation": sat.elevation,
"sun_alt": sun_alt,
"duration": duration,
"visible": visible
}

• Nice work, I wonder if there is a way you can use some of those angle libraries to predict things like Iridium flares. Heavens-above does it, but I've always figured it's some non-trivial math going on there. – Jack Apr 23 '14 at 22:11

My suggested improvement of harryissac's code, is offered in order to eliminate any exception caused by a None value (which 'will' occasionally occur without this fix). This is only a snippet that will eliminate the possibility of an Exception from any None tuple values. This seemed too long to put in a comment, so I offer it here.

This should all be indented one level to match harry's code. Apologies for not yet being that familiar with formatting here. After this part of code from harryissac:

now = datetime.datetime.utcnow()
observer.date = now

tr, azr, tt, altt, ts, azs = observer.next_pass(sat)


insert this code with or without its comments. The comments help explain:

# --- improvement to eliminate any None values -- START ------------

while (tr == None or
tt == None or
ts == None or
ts < tr
):

# Sometimes PyEphem needs a nudge to a later time in order for
# next_pass() to be fully populated with valid data. Therefore
# as long as there remains any None value returned, continue to
# re-compute in 15 minute jumps until any None is replaced with
# valid data.  15 minute jumps should produce a fully populated
# tuple before yet another pass begins.  A valid time should
# ensure a valid azimuth as well.  This fix catches all the
# Exceptions I 'have' encountered in similar earth satellite
# code, such as for ISS, when using PyEphem.

observer.date = now + datetime.timedelta(minutes=15)
sat.compute(observer)
tr, azr, tt, altt, ts, azs = observer.next_pass(sat)

# --- improvement to eliminate any None values -- END --------------


If anyone has a shorter version, or correction, go ahead and comment.

If a more complete explanation is desired for why this fix is necessary, post a comment and I will edit in a more thorough explanation. PyEphem documentation does warn of potential None values when using next_pass for earth satellites.

Brandon Rhodes has given us a feature rich PyEphem package by putting a python wrapper around Elwood Downey's Xephem code. There is definite useful benefit in knowing when it may return a None instead of some other expected value.

• @uhoh I like the reported accuracy of Skyfield and its speed using numpy. Presently I'm working with PyEphem, on a raspberry pi 3B+, and learning patience. About 16 seconds to get the next ~120 passes from the SVPOST TLEs, with no exception errors from None values. It needs to bump the search date quite a few times now and then, similar to what I just posted, in order to lose the None(s). And the TLEs, even with the most appropriate TLE epoch date selected, if I understand right, can still be a km or two off. Plenty accurate for my current purposes. I do plan a closer look at Skyfield. – always_learning Oct 25 '18 at 3:43
• Yes. Among the first comments in my PyEphem projects is 'think UTC and avoid the constantly changing fine details of local timezone politics, databases and code'. Most of my python projects are utterly tz naiive, like harryissac's code here, so they can be completely field portable (no internet connection necessary to run them except to update my TLE library before going into the field). – always_learning Oct 25 '18 at 4:52
• Yes, another reason I like pyephem. Simplicity. But I can appreciate Brandon's work to also develop and maintain a very very accurate package, that might require frequent online updates of one sort or another. – always_learning Oct 25 '18 at 6:03
• Clarification... after a refactor, the rpi 3B+ with pyephem does ~120 passes in ~4.5 seconds, and that too can probably be improved further. It reads and hunts through a TLE library with thousands of entries and that is no doubt a good percentage of that processing time, reading, processing, and selecting the most appropriate TLE for each pass searchdate. I didn't want to leave the rpi's abilities with pyephem with such a slow report, unexplained. – always_learning Nov 6 '18 at 18:57