- Swift, no dependenciesK-02
- OMM in CSV, JSON, XML, KVN, and TLEK-02
- SGP4 within 1 µm of the reference C++K-40
- MIT licenceK-07
Read GP data. Find passes. In plain Swift.
GPKit reads GP data (OMM and TLE), runs SGP4, and finds passes with look angles and Doppler. Catalog numbers go up to nine digits from OMM, and up to 339999 in a TLE.K-01K-02K-24K-29
In a TLE, catalog number 100000 is written A0000. GPKit reads that field into the Int 100000, then propagates the element set to find passes.
A0000: A is 10, and the last four digits follow. GPKit reads the field into the Int 100000 and never keeps the letter.G-10K-23K-24 Three lines
let iss = try ElementSets.read(csv, as: .csv).elementSets[0]
let passes = try Propagator(iss).passes(over: Observer(latitude: 40.0, longitude: -75.0), from: Epoch(.now), for: .seconds(86_400))
if let rise = passes.first?.rise { print(rise.time) }
Read CelesTrak's CSV for one satellite, find its passes over 40° N, 75° W in the next day, and print the first rise, in UTC. The package's tests compile this example.K-71
.package(url: "https://github.com/hneogy/GPKit.git", from: "1.0.0")
How data flows
Bytes in, element sets or refusals out; a propagator from each set, and passes from the propagator.K-11
Refusals
Nothing dropped without a word
A record that can't be read comes back as a refusal, with the reason and where it was. One bad record costs one record.K-25
let file = try ElementSets.read(bytes, as: .json)
for set in file.elementSets { set.catalogNumber } // 25544: an Int, whatever its width
for refusal in file.refusals { print(refusal) }
NORAD_CAT_ID "A0000" is not an integer of up to nine digits (record 2 of the file)
NORAD_CAT_ID in Alpha-5, which belongs to TLEs: the README's own words, which GPKit's tests compile and hold it to.K-71bK-24K-44Evidence
What's checked
- 2,349SGP4 steps against the reference C++, each within 1 micrometreVallado's SGP4.cpp, compiled as it is, over the 33 verification casesK-40
- 86,466states compared with python-sgp4 2.27; largest difference 0.0024 mm14,411 real element sets, each read by each library's own readerK-43
- 49passes against Skyfield 1.55; rises and sets within 0.17 sfive public element sets, two days eachK-42
- 68tests in 14 suitesthe README's examples among them, compiled and held to its wordsK-44
Against gpconf 0.8.0, 17 of 18 cases pass, each on every item exactly; the eighteenth checks data and runs no parser.K-83 The full result →
Why it exists
On 11 July 2026 the satellite catalog assigned number 100000. The usable five-digit range had already run out at 69,999 (CelesTrak).G-10G-11 A TLE has five columns for the number. Up to 339999 it can be written in Alpha-5; above that, only OMM can carry it.G-12 GPKit is built for data as it's served now:
- Catalog numbers are integers, from 0 to 999,999,999. Alpha-5 is decoded on the way in and never kept.K-24
- OMM in CSV, JSON, XML and KVN, and TLE, all read into one record type.K-02K-12
- Values kept as written. Element values are exact decimals, the source's own digits, not
Doubles.K-27 - Nothing dropped without a word. A record that can't be read comes back as a refusal, with the reason and where it was. One bad record costs one record.K-25
Where it runs
GPKit 1.0.0: macOS 13, iOS 16, tvOS 16 and visionOS 1 or later, and Linux. 1.0.0 doesn't build for watchOS: it counts
microseconds in Int, which is 32 bits on most watches.K-04K-05 The core is plain Swift; one file bridges to
Foundation's Date.K-14 GPKit makes no network request of its own.K-03
On main, not yet in a release: watchOS 9 and later, with what doesn't fit a 32-bit Int counted in Int64; and
Windows, Android and WebAssembly, where CI builds GPKit and runs its tests (on Android, in an emulator).K-90K-91K-92
Since 1.0.0, on main
Merged into GPKit's main since 1.0.0, and not yet in a release. Each is an addition: code written for 1.0 still
compiles.K-90
- The sun, and whether a pass can be seen. The sun's position, the sky at a place (daylight, twilight or dark), whether the satellite is sunlit, and when in a pass it can be seen with the eye.K-96
- More frames. Earth-fixed states, right ascension and declination from a place, ground tracks, and the footprint from which a satellite stands at a given elevation, checked against Skyfield 1.55.K-97
- Many satellites at once.
Propagator.statesruns a batch in parallel, with the same results as one at a time in the same build. On an Apple M5, 10,000 made-up element sets at one instant took 0.5 ms, against 1.7 ms one at a time; a day at one-minute steps took 0.37 s, against 2.1 s.K-95 - Antenna rotators. Where to point a rotator through a pass, within its limits, as Hamlib's set-position commands. In CI, Hamlib's dummy rotator follows a pass.K-98
- OEM. An ephemeris written as a CCSDS Orbit Ephemeris Message (OEM 2.0, KVN), which CI reads back with Python's
oempackage.K-99 - A rough close-approach screen.
CloseApproach.screenis a rough screen on GP data with SGP4: candidate close approaches, each with the two objects, the time, the miss distance and the relative speed. It gives no probability of collision and is not for operational collision avoidance. No approach found does not mean the objects are safe.K-100 - The
gpkittool.read,tle,passes,where,track,rotator,oemandapproaches, from a terminal. CI builds it for Linux, macOS and Windows; no release carries the builds yet.K-94 - pygpkit, GPKit for Python (
import pygpkit), with the numbers GPKit's own release build gives on the same system. CI builds and tests its wheel on Linux, macOS and Windows. It isn't on PyPI, and it isn't the unrelatedgpkitthere.K-93 - GPKitSources, a second library, asks CelesTrak for GP data and SatNOGS DB for active transmitters, under each provider's rules: an address at most once in two hours, and a stop at any answer that isn't a 200. GPKit itself still makes no request. SatNOGS DB's data is CC BY-SA 4.0. GPKitSources has had no release, and its interface may still change.K-101
- DocC tutorials. Three of them: your first ISS pass, a pass on a sky chart, and Doppler for your radio. The tests compile and run their code.K-102
Built on GPKit
GP Passes, an iPhone app for satellite passes, is built on GPKit.K-06
Sources
K-71The three-line example. Source: GPKit:README.md:5-15;ReadmeTests.swift:8-25.K-70The install snippet (SPM, from: "1.0.0"). Source: GPKit:README.md:29-38.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.G-10On 11 July 2026 the catalog assigned number 100000, to SARAMAGO. Source: gpconf:README.md:98-99;docs/BRAND.md:59.G-11The five-digit range ended at 69,999 (CelesTrak). Source: gpconf:docs/BRAND.md:59;docs/RESEARCH.md:32-36.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-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.K-02A Swift package with no dependencies: OMM (CSV, JSON, XML, KVN) and TLE readers, a TLE writer, SGP4, passes with look angles and Doppler. Source: GPKit:README.md:17-18;Package.swift:14.K-12One record type whatever the format; "the TLE is a way in, not the model". Source: GPKit:ElementSet.swift:3-4;README.md:64-65.K-27Values are ExactDecimal. Applied defaults are recorded indefaulted. Source: GPKit:ExactDecimal.swift:1-6;ElementSet.swift:54-76.K-25Refusals carry a kind (19 kinds), a message, a field and a line or record. One bad record costs one record. Source: GPKit:Refusal.swift:7-89;README.md:81-83.K-04Platforms: macOS 13, iOS 16, tvOS 16, visionOS 1. Linux is built in CI. Source: GPKit:Package.swift:9;.github/workflows/ci.yml:16.K-05No watchOS, because Int is 32 bits on most watches. Source: GPKit:Package.swift:7-8.K-14No Foundation outside Date.swift, enforced by a script in CI. Source: GPKit:Package.swift:14-15;tools/check-no-foundation.sh;ci.yml:24-31.K-03GPKit makes no network request of its own. Source: GPKit:README.md:14-15.K-90Merged into GPKit's main since 1.0.0 and not in a release: v1.0.0 is the only tag; each item below is under "Unreleased", as additions ("nothing is removed or changed"); within 1.x, code written for 1.0 keeps compiling. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):CHANGELOG.md:7-9,:92;README.md(Stability);git tag(v1.0.0 only); GitHub releases (v1.0.0 only).K-91watchOS 9 and later: GPKit builds for watchOS (arm64 and arm64_32), in CI on every push; what doesn't fit a 32-bitIntis counted inInt64. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):CHANGELOG.md:61,:65-68;README.md:19-21;.github/workflows/ci.yml:208.K-92GPKit builds and its tests run on Windows, on Android (in an emulator, swift.org's Android SDK for Swift 6.4.2) and for WebAssembly (WASI, swift.org's Wasm SDK), in CI. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):CHANGELOG.md:65-69,:72-73;README.md:19-23;.github/workflows/ci.yml:141-160,:163,:186-194.K-96Sun(the sun's position and height at a place, after the NOAA calculators' formulas),Sunlight(the sky at a place: daylight, twilight or dark; whether the satellite is sunlit; phase angle; a brightness estimate) andPassVisibility(when in a pass the satellite is sunlit and when it can be seen with the eye). Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):CHANGELOG.md:24-29;docs/public-api.swift:288,:301,:317,:331.K-97Earth-fixed states, right ascension and declination from a place, ground tracks, and coverage footprints, checked against Skyfield 1.55 in CI. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):CHANGELOG.md:30-37;docs/public-api.swift:142-147;conformance/frames-skyfield-2026-11-07.txt.K-95Propagator.states: many satellites at once, in parallel, each result the onestate(at:)gives in the same build; on an Apple M5 (release build), 10,000 made-up element sets at one instant 0.5 ms against 1.7 ms one at a time, and a day at one-minute steps 0.37 s against 2.1 s. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):CHANGELOG.md:38-41;conformance/benchmark-2026-11-07.txt(0.0005 s / 0.0017 s; 0.3705 s / 2.1409 s).K-98Rotator tracks: where to point an antenna rotator through a pass, within its limits, as Hamlib's set-position commands (gpkit rotator, to pipe to rotctl); in CI, Hamlib's dummy rotator follows a pass. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):CHANGELOG.md:19-23;docs/public-api.swift:283,:286.K-99OEM.write: an ephemeris as a CCSDS Orbit Ephemeris Message (OEM 2.0, KVN), SGP4's states in TEME and UTC; in CI Python'soempackage reads it back. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):CHANGELOG.md:15-18;docs/public-api.swift:149.K-100CloseApproach.screen(andgpkit approaches): "a rough screen on GP data" with SGP4 giving candidate close approaches (the two objects, the time, the miss distance, the relative speed); "no probability of collision", "not for operational collision avoidance", "no approach found does not mean the objects are safe"; tested against closest approaches found by formula and by brute force. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):Sources/GPKit/CloseApproach.swift:15-29;README.md:247-261;Sources/gpkit-cli/main.swift:34-36,:251-253;CHANGELOG.md:11-14.K-94Thegpkitcommand-line tool:read,tle,passes,where,track,rotator,oemandapproaches; exit 0, 1 (GPKit refused something) or 2 (a command line it doesn't understand); CI builds it for release on Linux, macOS and Windows; a published release carries the builds, and none has been published since 1.0.0. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):README.md:230-245,:247-250;CHANGELOG.md:70-71;Sources/gpkit-cli/main.swift.K-93pygpkit, GPKit for Python (import pygpkit; not the unrelatedgpkiton PyPI): reading, TLE writing, SGP4 states, looks, map positions and passes, with the numbers GPKit's own release build gives on the same system (tests compare with==); CI builds and tests its wheel on Linux, macOS and Windows; not on PyPI. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):CHANGELOG.md:62-64;README.md:263-267.K-101GPKitSources, a second library in the package: asks CelesTrak for GP data and SatNOGS DB for active transmitters, each address at most once in two hours, a stop at any answer that is not a 200, nothing repeated; GPKit itself makes no request; SatNOGS DB data is CC BY-SA 4.0; Space-Track is not among the sources; no release yet, its interface may change. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):README.md:269-276;CHANGELOG.md:49-59.K-102DocC documentation with three tutorials (your first ISS pass, a pass on a sky chart, Doppler for your radio); their code is compiled and run in the tests, and CI builds the documentation. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):CHANGELOG.md:80-81;README.md:337-338;Sources/GPKit/GPKit.docc/Tutorials/.K-06The GP Passes app is built on GPKit. Source: Passes:Modules/Package.swift:20.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-07Licence MIT, © 2026 NEOGY LLC. Source: GPKit:LICENSE:1-3.K-01"GPKit reads GP data — OMM and TLE, including six-digit catalog numbers. Against gpconf 0.8.0 it passes 17 of the 18 cases, and none fails; the eighteenth checks data and runs no parser.". Source: GPKit:README.md:3-4on main at 6d6ce58.K-29The TLE writer uses exact decimal rounding, Alpha-5 from 100000, and refuses numbers above 339999. Source: GPKit:TLE.swift:242-354;README.md:121-133.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-71bThe Reading example (ElementSets.read,elementSets,refusals, the printed refusal). Source: GPKit:README.md:42-62.K-4468 tests in 14 suites; the README examples are compiled by tests. Source: GPKit: grep of@Testand@Suite;ReadmeTests.swift:5-6.K-43python-sgp4 2.27: 14,411 element sets, 86,466 states (0, 90, 720, 1,440, 4,320 and 10,080 min), largest difference 0.0024 mm, no differing error codes. Not run in CI. Source: GPKit:conformance/python-sgp4-crosscheck-2026-10-07.txt:5-10;CrossCheckTests.swift:5-14.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.K-83gpconf 0.8.0 (PyPI), corpus 0.8.0, run 2026-11-05 00:36:31Z: GPKit 1229da2 (1.0.0's code), debug, macOS 27.2 arm64, Swift 6.4.2, Python 3.14.6; the maintainer's provider files of 2026-09-21 (128), network closed; cases 18, pass 17, pass-tolerance 0, fail 0, skip 0, not-fetched 0, not-available 0, not-exercised 1 (satcat-70000-cutoff); items pass 367, info 106, not-exercised 1; gate 256/256 TLE and CSV; report SHA-256 f858f93f…0a62; the commands; per-case verdicts and item counts. Source: GPKit:conformance/gpconf-0.8.0-2026-11-05.txton main at 6d6ce58 (PR #1, merged 2026-11-05T00:42:49Z; first read at 216459a).