satpass
satpass tells you when the ISS, Tiangong, Hubble, Starlink trains and the brightest rocket bodies will pass over your location, and whether you will be able to see them: the satellite has to be sunlit while your sky is dark. It works entirely offline from two-line element sets, using an SGP4/SDP4 propagator written from scratch after Vallado et al. (2006). That propagator matches the official reference vectors to better than a millimetre. The output can be a pass table, a live position readout, CSV tracks, SVG sky plots and ground-track maps, a "what's up tonight" list, or an iCalendar feed.
Features
- SGP4 and SDP4 written from scratch. Near-Earth and deep-space orbits are both handled,
including 12-hour and 24-hour resonance, lunar-solar perturbations and the Lyddane fix for
low inclinations. It uses WGS-72 constants, outputs TEME, and is verified against
tcppver.out. - TLE handling. 2-line and 3-line sets with checksum validation and epoch decoding. You can
look up a satellite by name, by alias (
ISS,CSS/TIANGONG,HUBBLE/HST) or by NORAD id. - CelesTrak updates.
satpass tle updatedownloads into a local cache, refreshes at most every 2 hours and warns when elements are stale. Sample TLEs are bundled, so the tool works offline straight away. - Frames. TEME→ECEF uses IAU-82 GMST, the observer's position uses WGS-84, and the topocentric SEZ frame gives azimuth, elevation, range and range-rate.
- Visibility. A low-precision Sun, a conical umbra/penumbra Earth-shadow model (plus a cylindrical test), twilight classes, and a diffuse-sphere magnitude estimate.
- Pass prediction. A coarse scan, then Brent root-finding for AOS and LOS and Brent maximisation for culmination, plus the visible windows inside each pass.
- Commands:
passes: list upcoming passes of one satellite.now: the satellite's current position and illumination.track: a position table as CSV.skyplot: a polar SVG plot of one pass.groundtrack: an SVG world map with Natural Earth coastlines.flyover-list: every bright pass from a group, for example tonight.ical: passes as an iCalendar feed.tle update|list|infoandconfig init|show: manage TLEs and your location.
Install
cd satpass
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
# or: python3.12 -m venv .venv && .venv/bin/pip install -e '.[dev]'
The only runtime dependency is click. pytest and sgp4 are needed only for the test suite;
sgp4 serves as the test oracle.
Quickstart
# 1. Tell satpass where you are (written to ~/.config/satpass/config.toml)
satpass config init --lat 12.9141 --lon 74.856 --elev 22 --tz Asia/Kolkata --name Mangaluru
# 2. Optional: fetch fresh elements (otherwise the bundled samples are used)
satpass tle update --group visual --group stations
# 3. Ask
satpass passes ISS --days 5 --min-el 10 --visible-only
satpass flyover-list --tonight
Every example below runs offline against the bundled TLEs and examples/config.toml, with a
fixed --start so you get the same output:
alias sp="satpass --config examples/config.toml"
Usage
passes: upcoming passes of one satellite
$ satpass passes CSS --start 2026-10-16T00:00 --days 5 --min-el 10 --visible-only
note: using bundled sample TLEs; run `satpass tle update` for fresh data
CSS (TIANHE) [48274] passes over Mangaluru (12.9141, 74.8560, 22 m) · times in Asia/Kolkata
window 2026-10-16 00:00 + 5 d, min elevation 10°, visible only
# Date AOS Az Culmination El Az LOS Az Mag Visible window
1 2026-10-16 19:35:07 SSW 19:40:12 35° SE 19:45:17 ENE -0.7 19:35:07-19:38:26
2 2026-10-17 05:23:54 NW 05:28:59 36° NE 05:34:04 ESE -1.9 05:26:26-05:34:04
3 2026-10-17 18:36:17 SSW 18:40:49 14° SE 18:45:21 ENE -1.6 18:36:17-18:42:32
4 2026-10-17 20:12:30 WSW 20:17:30 27° NW 20:22:30 NE 2.1 20:12:30-20:14:48
5 2026-10-18 04:24:48 NNW 04:29:27 16° NE 04:34:06 E 1.1 04:30:28-04:34:06
6 2026-10-18 19:12:46 SW 19:17:59 75° NW 19:23:12 NE -3.4 19:12:46-19:18:49
7 2026-10-19 05:01:34 NW 05:06:46 74° SW 05:11:58 SE -3.6 05:06:46-05:11:58
8 2026-10-19 19:50:56 W 19:55:20 12° NW 19:59:44 NNE 0.7 19:50:56-19:54:59
9 2026-10-20 04:02:07 NW 04:07:14 37° NE 04:12:19 ESE 2.4 04:10:46-04:12:19
10 2026-10-20 05:39:40 W 05:43:51 10° SW 05:48:04 S -1.3 05:43:01-05:48:04
11 2026-10-20 18:50:41 WSW 18:55:39 26° NW 19:00:38 NNE -1.4 18:50:41-18:58:47
- Mag is the brightest magnitude during the visible window. Lower is brighter.
- Visible window is when the satellite is sunlit and the Sun is below −6°. A 19:35 pass that is visible only until 19:38 means the satellite entered Earth's shadow at that point.
- A
<or>after a row means the pass was already in progress at the start of the search window, or was still in progress at its end.
Options:
--days N(default 5)--min-el DEG: minimum culmination elevation (default 10)--visible-only--start ISO: a time without an offset is read in your configured time zone.
now: where is it right now?
$ satpass now ISS --at 2026-10-16T14:19:20
ISS (ZARYA) [25544] at 2026-10-16 14:19:20 IST
azimuth 235.35° (SW)
elevation 66.11°
range 457.7 km range-rate -0.079 km/s
sub-point 12.018° lat 73.542° lon
altitude 421.0 km
velocity 7.660 km/s (inertial)
illumination sunlit (lit fraction 1.00)
sun altitude 52.41° (day)
magnitude 2.4
TLE epoch 2026-10-15 19:43:34 UTC (+0.55 d)
track: CSV time series
$ satpass track ISS --start 2026-10-16T14:15 --duration 2m --step 30s
time_utc,az_deg,el_deg,range_km,range_rate_km_s,lat_deg,lon_deg,alt_km,sunlit,sun_alt_deg,magnitude
2026-10-16T08:45:00Z,318.166,4.526,1908.813,-6.8542,24.7561,63.0042,423.067,1,53.28,-0.15
2026-10-16T08:45:30Z,317.481,7.032,1703.714,-6.8157,23.3279,64.3172,422.766,1,53.18,-0.31
2026-10-16T08:46:00Z,316.584,9.964,1500.128,-6.7514,21.8865,65.5998,422.479,1,53.08,-0.48
2026-10-16T08:46:30Z,315.367,13.513,1299.036,-6.6459,20.4330,66.8544,422.207,1,52.98,-0.66
2026-10-16T08:47:00Z,313.632,17.994,1102.077,-6.4690,18.9685,68.0832,421.950,1,52.88,-0.83
--duration and --step accept s, m, h and d suffixes. -o file.csv writes the table
to a file.
skyplot: polar plot of one pass
$ satpass skyplot CSS --pass 1 --start 2026-10-16T00:00 --visible-only -o docs/img/css-pass.svg
wrote docs/img/css-pass.svg: CSS (TIANHE) pass 1, 2026-10-16 19:35:07 max 34.7°
The pass number refers to the same list passes prints with the same options. In the plot, the
zenith is at the centre and the horizon is the outer ring. The path is drawn:
- yellow where the satellite is visible
- blue where it is sunlit but the sky is too bright
- dashed grey where it is in Earth's shadow
Each minute is marked with a tick.
groundtrack: map of the sub-satellite path
$ satpass groundtrack ISS --orbits 3 --start 2026-10-16T18:00 -o docs/img/iss-track.svg
wrote docs/img/iss-track.svg: ISS (ZARYA), 3 orbits (279 min)
The map uses an equirectangular projection with embedded Natural Earth 1:110m coastlines (public domain, simplified to 0.1°). The track is solid where the satellite is sunlit and dashed where it is in shadow. The track is split cleanly where it crosses the antimeridian.
flyover-list: everything bright tonight
$ satpass flyover-list --group visual --tonight --start 2026-10-16T12:00
Bright passes (bundled:visual, 155 satellites) over Mangaluru (12.9141, 74.8560, 22 m) · times in Asia/Kolkata
2026-10-16 18:32 -> 2026-10-17 05:59, min el 20°, mag <= 3.5
Start End Mag VisEl Dir Satellite
18:32:40 18:41:37 1.9 79° S -> N COSMOS 1953 [19210]
18:33:25 18:43:45 2.7 31° NNW -> SE SPACEMOBILE-006 [67232]
18:36:21 18:44:34 1.6 29° SSE -> N CZ-2C R/B [54039]
18:37:16 18:50:14 3.4 69° N -> SSW SAOCOM 1A [43641]
19:33:24 19:39:35 2.2 72° SW -> ENE XRISM [57800]
19:35:07 19:38:26 -0.7 18° SSW -> ENE CSS (TIANHE) [48274]
05:02:44 05:15:44 2.5 45° SSW -> N SL-14 R/B [17912]
...
05:44:57 05:53:38 3.4 24° WNW -> SE XRISM [57800]
--tonightsearches from the evening's −6° Sun crossing to the morning one. If it is already dark, the search starts now.- Without
--tonight,--hours Nsets the length of the window. - VisEl is the highest elevation reached while the satellite is visible.
- Other options:
--min-el(default 20) and--max-mag(default 3.5). - The whole 155-object group takes under a second.
ical: subscribe in your calendar
$ satpass ical CSS --start 2026-10-16T00:00 --days 14 -o css.ics
wrote css.ics: 13 events
BEGIN:VEVENT
UID:48274-20261016T141012Z@satpass
DTSTART:20261016T140507Z
DTEND:20261016T140826Z
SUMMARY:CSS (TIANHE) pass\, max 35° SE\, mag -0.7
DESCRIPTION:AOS 14:05:07 UTC az 213° (SSW)\nMax 14:10:12 UTC el 34.7° az
136°\nLOS 14:15:17 UTC az 59° (ENE)\nVisible to the naked eye during thi
s event.
Events cover the visible window. By default only visible passes are exported; add
--all-passes to include every pass above --min-el. Lines are folded to 75 octets as
RFC 5545 requires.
tle: element sets
$ satpass tle update --group visual
visual: 155 element sets downloaded to /home/you/.cache/satpass/visual.tle
$ satpass tle update --group visual
visual: 155 element sets fresh in /home/you/.cache/satpass/visual.tle
$ satpass --tle examples/geo.tle tle info GSAT-30
GSAT-30 NORAD 45026 COSPAR 20005A
1 45026U 20005A 26288.95331432 -.00000164 00000+0 00000+0 0 9990
2 45026 0.0126 241.5401 0002275 287.4500 281.7263 1.00273494 24710
epoch 2026-10-15 22:52:46.357248 UTC
inclination 0.0126° RAAN 241.5401°
eccentricity 0.0002275 arg perigee 287.4500°
mean motion 1.00273494 rev/day (period 1436.07 min)
B* 0.00000e+00
model deep-space (SDP4)
tle updatetakes--groupmore than once to fetch several groups (default:visualandstations).--forcebypasses CelesTrak's courtesy limit of one download per 2 hours.tle listshows each element set's epoch and age.- Any command warns when the elements are more than 7 days from the prediction time, and again more strongly past 30 days.
Configuration
The location file is TOML (see examples/config.toml):
[observer]
name = "Mangaluru"
lat = 12.9141 # degrees, north positive
lon = 74.856 # degrees, east positive
elev = 22.0 # metres above the WGS-84 ellipsoid
tz = "Asia/Kolkata" # IANA zone used for displayed times
Lookup order:
--config PATH$SATPASS_CONFIG$XDG_CONFIG_HOME/satpass/config.toml(normally~/.config/satpass/config.toml)
--lat/--lon/--elev/--tz override the file, or replace it if you give both lat and lon.
Where TLEs are read from:
--tle FILE, which uses only that file.- The download cache
$SATPASS_CACHE_DIR, otherwise~/.cache/satpass/*.tle. - The bundled samples.
When several sets share a NORAD id, the one with the newest epoch wins.
Global options such as --config, --tle and --lat go before the subcommand:
satpass --tle my.tle passes ISS.
Bundled sample TLEs
These were downloaded from CelesTrak on 2026-10-16 and are copied in examples/ and in the
package:
| File | Sets | Epochs (UTC) |
|---|---|---|
visual.tle |
155 | 2026-10-13 18:28 → 2026-10-16 07:57 |
stations.tle |
17 | 2026-10-15 07:27 → 2026-10-16 07:57 |
starlink.tle |
10 | 2026-10-15 12:54 → 2026-10-16 06:17 |
geo.tle |
9 (GSAT/INSAT/TDRS/GOES/…) | 2026-10-15 14:09 → 2026-10-16 05:25 |
They are good for predictions within about a week of those dates.
Architecture
| Module | Role |
|---|---|
tle.py |
TLE parsing, checksum, epoch → datetime/JD, lazy propagator |
sgp4.py |
SGP4 initialisation and propagation (near-Earth core) |
deepspace.py |
SDP4 resonance setup (dsinit) and integrator (dspace) |
lunisolar.py |
Lunar-solar coefficients (dscom) and periodics (dpper, Lyddane) |
constants.py |
WGS-72 constants |
frames.py |
GMST, TEME↔ECEF, WGS-84 geodetic, SEZ look angles, Observer |
sun.py |
Sun ephemeris, conical/cylindrical shadow, phase angle, twilight |
magnitude.py |
Standard magnitudes and diffuse-sphere brightness |
rootfind.py |
Brent root finder and Brent maximiser |
passes.py |
observe(), pass search, visibility windows, dark interval |
svg.py |
Sky plot and ground-track renderers |
ical.py |
RFC 5545 export |
catalog.py |
Bundled/cached sources, CelesTrak download, name lookup, age warnings |
config.py |
TOML location config |
cli.py, cli_admin.py, clictx.py |
Click commands |
docs/algorithms.md explains the maths with worked examples:
- SGP4 and SDP4
- the reference frames
- shadow geometry and magnitudes
- the pass search
- accuracy versus TLE age
Tests
.venv/bin/pytest -q
The suite has 112 tests:
- Propagator against Vallado. It is checked against Vallado's reference vectors for 22 satellites (near-Earth, deep-space and all resonance classes), including error codes 4 and 6.
- Propagator against the oracle. Modern TLEs, including GEO, are compared with the
sgp4package at random times. - TLE parsing and checksums.
- Frames. GMST against Vallado Example 3-5, the TEME→ECEF example, geodetic round trips and look-angle geometry.
- Sun and shadow. Sun position against Vallado Example 5-1; the shadow models in umbra, penumbra and annular geometry.
- Magnitudes and root finding.
- Pass prediction. Results are compared with a 1-second brute-force scan driven by the oracle (AOS/LOS within 1.5 s, peak elevation within 0.01°), and visibility windows are checked for consistency.
- Output files. The SVGs are checked to be well-formed and the iCal structure is checked.
- Cache behaviour. Uses a stubbed download.
- The CLI end to end. Runs offline.
Limitations
- Accuracy is limited by the TLE. Predictions are only as good as the elements: expect tens of seconds of error after a week, and much more for objects in low orbit during high solar activity. See the accuracy table in the docs.
- Earth orientation is simplified. Polar motion is ignored and UT1 is taken as UTC, which costs at most a few hundred metres. Atmospheric refraction is ignored too, so it does not lift objects near the horizon by about 0.5°.
- Magnitudes are rough. Standard magnitudes are built in only for a few objects (ISS, Tiangong, HST, a few Earth-observation satellites and Starlink). Everything else uses a generic 4.0. Flares and glints are not modelled.
- Very short passes can be missed. The coarse scan uses a 60 s step for LEO, so a grazing pass that lasts under a minute and stays within a degree or so of the horizon can be skipped. Any pass above 10° is found.
- Always-up satellites are one clipped pass. A geostationary satellite that never sets is
reported as a single pass flagged
<>. Its "visible window" means sunlit against a dark sky, which is not the same as visible to the naked eye; GEO satellites are around magnitude 10–12. - Only the "improved" SGP4 mode is implemented. The legacy AFSPC mode differs slightly in how GMST is computed at epoch and in some angle wrapping.