How a pass prediction works
GP Passes turns a few lines of numbers into "look north-north-east at 3:45 AM". It does it in five steps, all on your phone.A-01 Each step has a short version, then a "Go deeper" section with the maths and the evidence for it.
We'll follow one real pass all the way through. The satellite is SARAMAGO, a small Portuguese satellite. It was the first object ever to be given catalog number 100000, on 11 July 2026.G-10E-01 The place is 40° N, 75° W, in the eastern United States.E-03
1. Element sets: the orbit, as numbers
A satellite's orbit can be written down as a handful of numbers, measured at one moment. That's called an element set. From it you can work out where the satellite will be at any time, much as you'd work out where a ship will be from its last known position, speed and heading.A-51
GP Passes gets element sets in two ways:
- Download. Tap Download and the app asks CelesTrak, a free service, for its amateur radio group, which includes the ISS.A-10A-11E-10 It asks once, and not again for two hours.A-25
- Your own file. Import an element-set file from the Files app (in Detailed mode).A-12A-47
Element sets age. The app shows each one's age, suggests newer ones after three days, and says passes from one over fourteen days old are not reliable.A-16
Go deeper: what's in an element set
Here is SARAMAGO's first published element set, as a two-line element set (TLE).E-01E-02
SARAMAGO
1 A0000U 26067CY 26195.90649229 .00004770 00000+0 22159-3 0 9994
2 A0000 97.4593 154.0970 0005590 270.5113 89.5482 15.20467281 15911
Data: CelesTrak.E-01
- Line 1
- 1 line number (column 1)
- A0000 catalog number (columns 3–7)
- U classification (column 8)
- 26067CY international designator (columns 10–17)
- 26195.90649229 epoch (columns 19–32)
- .00004770 first derivative of mean motion (columns 34–43)
- 00000+0 second derivative of mean motion (columns 45–52)
- 22159-3 B*, the drag term (columns 54–61)
- 0 ephemeris type (column 63)
- 999 element set number (columns 65–68)
- 4 checksum (column 69)
- Line 2
- 2 line number (column 1)
- A0000 catalog number (columns 3–7)
- 97.4593 inclination (columns 9–16)
- 154.0970 right ascension of the ascending node (columns 18–25)
- 0005590 eccentricity (columns 27–33)
- 270.5113 argument of perigee (columns 35–42)
- 89.5482 mean anomaly (columns 44–51)
- 15.20467281 mean motion (columns 53–63)
- 1591 revolution number (columns 64–68)
- 1 checksum (column 69)
| field | in this set | means |
|---|---|---|
| catalog number | A0000 |
100000, in Alpha-5 (below) |
| international designator | 26067CY |
the launch label: launch year 2026, launch number 067 of that year, and object CY from that launch |
| epoch | 26195.90649229 |
day 195.906… of 2026: 14 July 2026, 21:45 UTC. The moment the numbers describe |
| B* | 22159-3 |
0.00022159 per Earth radius: the drag term SGP4 uses for the thin air at that height |
| inclination | 97.4593 |
the tilt of the orbit against the equator, in degrees. 0° is right above the equator; 90° runs from pole to pole |
| eccentricity | 0005590 |
0.0005590: how stretched the orbit is. 0 is a perfect circle, so this one is very nearly round |
| mean motion | 15.20467281 |
orbits per day, so one orbit takes about 94.7 minutes |
E-02K-17A-58 The rest (right ascension of the ascending node, argument of perigee, mean anomaly) fix where the orbit lies and where the satellite is on it.
TLE and OMM. A TLE has fixed columns, and its catalog number has room for only five characters. An OMM (Orbit Mean-Elements Message, CCSDS 502.0-B-3) carries the same elements as named fields, in CSV, JSON, XML or KVN, with room for catalog numbers of up to nine digits.G-12K-22K-24 The app reads all five formats.A-46
Catalog numbers above 99,999. The catalog passed 99,999 on 11 July 2026, when number 100000 was assigned.
The usable five-digit range had already run out at 69,999 (CelesTrak).G-10G-11 For TLEs, a stopgap called
Alpha-5 writes the first two digits of a six-digit number as one letter: A = 10 through Z = 33, skipping I and O.
So 100000 is A0000, and the most it can hold is 339999.G-12K-23 Space-Track serves Alpha-5 and CelesTrak
doesn't: CelesTrak's TLE answers leave out every object above 99999, while its OMM formats carry them all.G-12
The app downloads CSV, an OMM format.A-10 SARAMAGO's TLE above was written from CelesTrak's OMM by gpconf, since
CelesTrak serves no TLE for it.E-01
Why sets age. Even at a satellite's height there's a trace of air, and it changes low orbits a little every day, so passes drift from what old numbers say.A-51 Ages: up to 3 days is current, over 3 is ageing, over 14 is not reliable.A-16
2. SGP4: from numbers to a position
SGP4 is the standard method for running an element set forward in time. It's what TLEs are made for.A-58 Give it an element set and a time, and it gives the satellite's position and velocity.K-11 The app uses GPKit's SGP4, a Swift port of David Vallado's SGP4.cpp.A-04K-30
SARAMAGO's set describes 14 July at 21:45 UTC (17:45 local time at the example place). To find its passes over the next two days, SGP4 is run forward from there, again and again.E-03
Go deeper: frames, constants and checks
- What SGP4 allows for. The Earth's shape (the J2, J3 and J4 terms of its gravity) and the thin air, through B*. For orbits of 225 minutes or longer it adds the pull of the Sun and Moon, and the resonance terms for half-day and one-day orbits.A-58K-31K-32
- Constants. WGS-72, the constants element sets are fitted with, is the default.K-31
- From space to your sky. SGP4 gives a position in TEME, a frame fixed to the stars, in kilometres. GPKit turns it with the Earth by Greenwich sidereal time, then measures it from your place on the WGS-84 ellipsoid. UT1 is taken to be UTC (they're always within 0.9 s), and the wobble of the pole is left out.K-34K-35
- How it's checked.
- GPKit's port runs side by side with the original C++, compiled as it is, through the 33 standard verification cases: 2,349 steps, 24 of the cases deep-space orbits. It agrees with the reference C++ within 1 micrometre, with the same error codes.K-40
- Against Skyfield 1.55, an independent Python library, 49 passes of five satellites agree: rises and sets within 0.17 s, highest points within 0.35 s.K-42
- How GPKit is tested →
3. Rise, highest point, set
A pass has three moments: Rise, when the satellite clears your horizon; Highest, the top of its climb; and Set, when it goes back below.A-51 The app looks ahead 3, 5 or 10 days (3 unless you choose more).A-17
To find them, the app checks how high the satellite is every 30 seconds. When it crosses the horizon between two checks, it narrows the moment down to a millisecond. Then it finds the top of the climb.K-50K-51
SARAMAGO, 15 July 2026, from 40° N, 75° WE-03E-06:
| UTC | local time at the example place (EDT, UTC−4) | height | direction | |
|---|---|---|---|---|
| Rise | 07:45:08 | 03:45:08 | 0° | NNE (17.5°) |
| Highest | 07:50:55 | 03:50:55 | 48.2° | E (99.4°) |
| Set | 07:56:37 | 03:56:37 | 0° | S (181.1°) |
About eleven and a half minutes above the horizon. At its highest it is 668 km away.E-03 These are Skyfield's figures. In GPKit's recorded comparison, its own times for this satellite's passes are within 0.12 s of them.E-04
GPKit's own figures for this pass: rise at 07:45:08.36 UTC, highest at 07:50:54.69 (48.21°, az 99.39°, 667.9 km), set at 07:56:36.61.E-09
Go deeper: the search
- Crossings are found by bisection: halve the interval, keep the half where the height changes sign, and stop under a millisecond.K-51
- The highest point is found by golden-section search in the minute around the highest sample.K-51
- Short passes. A pass that rises and sets between two samples is still found: a peak between samples that comes close to the horizon is looked into.K-52
- Geometric angles. The atmosphere bends light near the horizon, so a satellite can be seen a little before the geometric rise. The app doesn't allow for that.K-53
- Your horizon. Choose a minimum height (0°, 5°, 10°, 15°, 20° or 30°; the horizon by default), for every satellite or for one. Or draw your skyline in eight directions with the horizon mask, and each pass is cut to where the satellite clears it.A-18A-44A-45
- When an orbit runs out. If a satellite decays partway through the look-ahead, the search stops there and keeps the passes it found.K-54
4. Sunlight and darkness: can you see it?
Satellites have no lights of their own. You see sunlight bouncing off them. So you can see one only when two things are true at once: the satellite is in sunlight, and your sky is dark.A-51A-14 That's why the best time to look is usually the few hours after dusk and before dawn.A-51
The app marks each pass with what it foundA-15:
- "Can be seen": in sunlight while your sky is dark, for all of the pass or for the times shown.
- "Daylight" or "Twilight": the sky is too bright.
- "In the Earth's shadow": the sky is dark, but no sunlight reaches the satellite.
"Can be seen" means the light is right. Whether you see it also depends on how bright the satellite is and on your sky.A-15 For a radio, every pass counts: radio doesn't need sunlight.A-51
SARAMAGO's pass: can be seen from its rise at 3:45 AM until 3:52 AM, when it goes into the Earth's shadow. The sky is dark, with the sun 17° below the horizon.E-05E-06
Go deeper: the sun and the shadow
- The sun's position is worked out the way NOAA's solar calculators do it, after Jean Meeus's Astronomical Algorithms. Against the US Naval Observatory's figures it's good to a few thousandths of a degree, which moves a sunset by about a second.A-15
- Dark means the sun is more than 6° below the horizon: the end of civil twilight. Between 0° and −6° is twilight.A-14A-15
- The Earth's shadow is taken as a cylinder as wide as the equator, straight behind the Earth. The real shadow tapers a little, has a soft edge and is narrower over the poles. So for a low orbit, the moment a satellite goes in or out can be a few seconds off.A-15
- Each pass is checked every 10 seconds (at most 240 times), and each change into or out of sunlight or darkness is narrowed to the second.A-15
- This step is GP Passes' own code. GPKit finds passes; it doesn't model the sun.K-55
5. Where to look
The app gives every moment as a direction and a height.A-19
- Direction: one of 16 compass points, like NNE. These are true directions, as on a map.A-19A-58
- Height: degrees above the horizon. The horizon is 0° and straight up is 90°. Your fist held at arm's length covers about 10°.A-52
For SARAMAGO: face north-north-east at 07:45 UTC (03:45 local time at the example place) and look low. It climbs to almost halfway up the sky in the east, then sinks towards the south.E-03
The sky view draws the whole pass on a disc: the middle is straight overhead, the edge is your horizon, north is up and east is to the right, as on a map.A-19A-51
Go deeper: azimuth and elevation
- Azimuth is degrees clockwise from true north: 90° is east, 180° south. Elevation is degrees above the horizon.K-16A-58
- The compass point is the nearest of 16, each 22.5° wide.A-19
- On the sky view, a direction lands at a distance from the centre proportional to 90° minus its elevation, at the angle of its azimuth.A-19
Sources
A-01Passes are worked out on the phone, from element sets, using GPKit's SGP4. Source: Passes:Modules/Sources/PassesCore/Passes.swift:116-129;Modules/Package.swift:24-25.G-10On 11 July 2026 the catalog assigned number 100000, to SARAMAGO. Source: gpconf:README.md:98-99;docs/BRAND.md:59.E-01SARAMAGO's first published element set, as a TLE rendered by gpconf from CelesTrak's OMM (gp-first.php?CATNR=100000&FORMAT=CSV, retrieved 2026-09-21T00:43:55Z), because CelesTrak serves no TLE above 99999. Source: gpconf:derived/alpha5-tle/alpha5-A-100000-saramago-first.tleand.provenance.json; the same lines in GPKitTests/GPKitTests/Resources/pass-check-sets.tle(NOTICE:56-61, from gpconf at bec466b).E-03Over 40.0° N, 75.0° W (100 m), from 2026-07-14T21:45 UTC for two days, Skyfield 1.55 gives a SARAMAGO pass: rise 2026-07-15 07:45:08 UTC at az 17.5°; culmination 07:50:55 UTC, el 48.21°, az 99.4°, range 667.9 km; set 07:56:37 UTC at az 181.1°. Source: GPKit:Tests/GPKitTests/Resources/skyfield-passes.json(set "SARAMAGO"), written bytools/skyfield_passes_reference.py.A-51The six lessons and their titles, in order. Source: Passes:PassesCore/Lessons.swift:13,84-135.A-10The app downloadshttps://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=CSV, and only when you tap Download. Source: Passes:PassesCore/Download.swift:34;PassesUI/AppModel.swift:438-440("Nothing else in the app calls this"); callersSatelliteListView.swift:185-187.A-11Only one source can be chosen in a release build: CelesTrak's amateur group. SatNOGS DB and an own server are not available. Source: Passes:PassesCore/DataSource.swift:117-140.E-10CelesTrak's amateur group includes the ISS. Source: Passes:DataSource.swift:153(app text).A-25At most one request every two hours. The attempt is counted before it is sent, and there are no retries. Source: Passes:Download.swift:146-161;DataSource.swift:98.A-12You can import element-set files from the Files app. Source: Passes:SatelliteListView.swift:161-169.A-47Import is through the Files picker, and only in Detailed mode. There is no paste and no share-sheet import. Source: Passes:SatelliteListView.swift:113-169.A-16An element set's age is shown, and it is marked "not reliable" past two weeks. Source: Passes:PassesCore/Lessons.swift:107-116;README.md:65-70.E-02The field reading of that TLE: designator 26067CY; epoch day 195.90649229 of 2026 = 14 July 2026 21:45 UTC; B* 0.22159e-3; inclination 97.4593°; eccentricity 0.0005590; mean motion 15.20467281 rev/day, so a period of about 94.7 min (1440 / 15.20467281 = 94.707). Source: the line itself; the field layout as GPKit reads it (TLE.swift); the arithmetic shown.K-17B* is stored per Earth radius; mean motion is revolutions per day. Source: GPKit:ElementSet.swift:24-25,39-40.A-58The app's glossary text, as quoted: SGP4 "The standard method for running an element set forward in time… It's what TLEs are made for"; azimuth "true directions, as on a map"; designator, inclination, eccentricity. Source: Passes:PassesCore/Glossary.swift:43,67,69,71,73.G-12Space-Track serves Alpha-5 and CelesTrak does not; a number above 339999 can't be written as a TLE. Source: gpconf:README.md:105-113,136-138.K-22OMM in CSV, JSON, XML (505.0-B-3) and KVN (502.0-B-3 §7). Quirks handled as listed. DOCTYPE refused. Source: GPKit:ElementSets.swift:1-13;XML.swift:1-7;README.md:105-119.K-24Catalog numbers are an Int; OMM NORAD_CAT_ID takes up to nine digits; Alpha-5 is refused in OMM. Source: GPKit:OMM.swift:4-17;ElementSet.swift:9-10;OMMTests.swift:58-70.A-46Import reads OMM in CSV, JSON, XML or KVN, and TLE (including .2le and .3le), detected by content. Source: Passes:FormatDetector.swift:4-44.G-11The five-digit range ended at 69,999 (CelesTrak). Source: gpconf:docs/BRAND.md:59;docs/RESEARCH.md:32-36.K-23Alpha-5 as Space-Track defines it: A–Z without I and O = 10–33, ceiling 339999. Test vectors. Source: GPKit:Alpha5.swift:1-46;ValueTests.swift:85-101.K-11Data flow:ElementSets.read→ElementSet→Propagator→ TEME state → Earth-fixed → look → passes. Source: GPKit:ElementSets.swift:54-69;OMM.swift:43-104;Propagator.swift:94-128;Observer.swift:102-154;Passes.swift:58-177.A-04Settings → About shows "Orbits: GPKit 1.0.0 (SGP4)". Source: Passes:Modules/Sources/PassesUI/SettingsScreen.swift:127.K-30SGP4 is a port of Vallado's SGP4.cpp (CelesTrak/fundamentals-of-astrodynamics 49df0479c950), with names and order of operations kept. Source: GPKit:NOTICE:9-17;SGP4Core.swift:1-15.K-31Gravity constants WGS-72 (default), WGS-84 and WGS-72-old. Source: GPKit:SGP4Core.swift:78-108;Propagator.swift:76-94.K-32Deep-space terms apply at a period of 225 min or more. Source: GPKit:SGP4Core.swift:44-45.K-34Positions are TEME; UT1 is taken as UTC (within 0.9 s); polar motion is left out. Source: GPKit:Observer.swift:102-104;README.md:218-219.K-35WGS-84 ellipsoid for the observer. Source: GPKit:Observer.swift:95-100.K-40SGP4 vs the unmodified C++: 33 cases (24 deep space), 2,349 steps, equal error codes, agreeing within 1 micrometre (asserted). Source: GPKit:SGP4Tests.swift:74-75,102-126.K-42Skyfield 1.55: five public element sets over 40° N, 75° W, two days each; 49 passes; 95 rises and sets within 0.166 s; 47 culminations within 0.349 s; look-angle bounds. Source: GPKit:conformance/skyfield-2026-10-07.txt:1-19; testPassTests.swift:168-248.A-17Look ahead 3, 5 or 10 days, default 3. Source: Passes:Passes.swift:101-102;PassFilter.swift:134.K-50Elevation is sampled every 30 s. Source: GPKit:Passes.swift:110.K-51Crossings are found by bisection to 0.5 ms; the highest point by golden-section search. Source: GPKit:Passes.swift:77-108.E-06Local time at the example place: US Eastern Daylight Time (UTC−4) on 14–15 July 2026, so rise 03:45:08, highest 03:50:55, set 03:56:37, epoch 17:45. Source: IANA time zone America/New_York through Python'szoneinfo, run 2026-10-31.E-04GPKit's recorded comparison for SARAMAGO: 9 passes, largest time difference 0.11986 s. Source: GPKit:conformance/skyfield-2026-10-07.txt:9.E-09GPKit's own figures for the example pass: rise 07:45:08.36 UTC at az 17.49°; highest 07:50:54.69 UTC, el 48.21°, az 99.39°, range 667.9 km; set 07:56:36.61 UTC at az 181.08°; 9 passes in the two days, as in E-04. Source:Propagator.passes(over:from:for:): scratch packagedocs/checks/site-figures/(GPKit v1.0.0, PassesCore at Passes cd6b3fd, E-01's lines), run 2026-11-07; outputdocs/checks/site-figures/output-2026-11-07.txt.K-52Passes shorter than a step are found. Source: GPKit:Passes.swift:147-156; testPassTests.swift:290-303.K-53No refraction; angles are geometric. Source: GPKit:README.md:216-217.A-18Minimum elevation 0, 5, 10, 15, 20 or 30°, default 0 ("The horizon"), settable per satellite. Source: Passes:ObserverLocation.swift:246-271;SettingsScreen.swift:185-187.A-44Horizon mask: eight directions, heights 0–60° in steps (0,5,10,15,20,30,45,60), joined in straight lines between directions. Passes are clipped every 5 s. Source: Passes:HorizonMask.swift:8-16,113-136;Passes.swift:134-139.A-45Passes that never clear the mask are counted. Source: Passes:Passes.swift:67-68.K-54A search stops at the first failure and keeps what it found. Source: GPKit:Passes.swift:29-31,164-175; testsPassTests.swift:313-367.A-14"Can be seen" means the satellite is in sunlight while your sky is dark (sun more than 6° below the horizon). Source: Passes:PassesCore/Sun.swift:14-15,145-147,268.A-15Sky categories: daylight (sun above 0°), twilight (0 to −6°), dark. Earth's shadow is a cylinder of radius 6378.137 km. The sun's position follows NOAA/Meeus. Passes are sampled every 10 s (at most 240 samples) and changes refined to the second. Source: Passes:Sun.swift:7-18,101-136,215-249.E-05The light of the example pass: sky dark, the sun 17° below the horizon at the highest point (−17.49°); in sunlight from rise until 07:52:57 UTC (3:52:57 AM EDT), then in the Earth's shadow; "Can be seen 3:45 AM to 3:52 AM". Source:PassLight.ofand itssummary,skyLine,sunlightLine: scratch packagedocs/checks/site-figures/(GPKit v1.0.0, PassesCore at Passes cd6b3fd, E-01's lines), run 2026-11-07; outputdocs/checks/site-figures/output-2026-11-07.txt.K-55GPKit has no horizon that varies by direction: a pass search takes one minimum elevation. (1.0.0 also had no sun or visibility model; 1.1.0 adds one, K-96). Source: GPKit v1.1.0 (tag at 0c4d396, released 2026-11-09):docs/public-api.swift:201(passes(over:from:for:above minimumElevation: Double = 0)), the only horizon parameter in the interface; v1.0.0: a grep of Sources for sun/eclipse/sunlit/twilight found nothing.A-19Directions use a 16-point compass. The sky view has the zenith at the centre and north up. Source: Passes:Display.swift:21-23;Passes.swift:186-196.A-52A fist at arm's length is about 10°. ISS FM repeater 145.990 up / 437.800 down, 67 Hz tone. A licence is needed to transmit. Source: Passes:Lessons.swift:84-90,127-135. The app cites its sources indocs/audit-2026-10/LEARNING.md§8.K-16Azimuth is degrees clockwise from north (90 is east); elevation is degrees above the horizon, negative below it; range is in km; range rate is in km/s, positive while receding. Source: GPKit:Observer.swift:64-73.E-11A TLE's fields by column, as GPKit reads them: line 1 has the line number (1), catalog number (3–7), classification (8), international designator (10–17), epoch (19–32), first and second derivatives of mean motion (34–43, 45–52), B* (54–61), ephemeris type (63), element set number (65–68) and checksum (69); line 2 the line number, catalog number (3–7), inclination (9–16), right ascension of the ascending node (18–25), eccentricity (27–33), argument of perigee (35–42), mean anomaly (44–51), mean motion (53–63), revolution number (64–68) and checksum (69); each line 69 characters. Source: GPKit v1.0.0 (01545c6, the version GP Passes 1.0 resolves: PassesModules/Package.resolvedat 3557f71):Sources/GPKit/TLE.swift:51(69 characters),:67-68,:72,:75,:78-79,:86-88,:98-112(the 0-based slices),:184(the line number).E-08The example pass's track for the sky view: azimuth and elevation every 10 s from rise to set, 70 points; its events reproduce E-03 (rise 07:45:08.5 UTC at 17.49°, highest 07:50:54.7 at 48.21°, 99.4°, 667.9 km; set 07:56:36.7 at 181.08°). Source:tools/sky_track.py, Skyfield 1.55 (the corpus's .venv), E-01's lines, offline; outputsites/passes/data/saramago-track.json.