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.

SARAMAGO was the first object numbered 100000, on 11 July 2026. A TLE writes it 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")

Get started → · GitHub →K-70

How data flows

Bytes in, element sets or refusals out; a propagator from each set, and passes from the propagator.K-11

How data flows through GPKitBytes go to ElementSets.read, which returns an ElementSetFile. Each bad record becomes a Refusal; each good one an ElementSet. Propagator is made from an ElementSet. It gives a StateVector in TEME with state(at:), a Look with look(from:at:), and Passes with passes(over:from:for:): a rise, a culmination and a set, each with a Look.bytesElementSets.read(_:as:)ElementSetFileper bad recordRefusal.elementSetsElementSetPropagator(_:)Propagatorstate(at:)StateVector (TEME)look(from:at:)Lookpasses(over:from:for:)Passes: rise · culmination · set

How it works →

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)

What it prints for a JSON file whose second record gives 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-44

Evidence

What's checked

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:

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

Built on GPKit

GP Passes, an iPhone app for satellite passes, is built on GPKit.K-06

Sources

  1. K-71 The three-line example. Source: GPKit: README.md:5-15; ReadmeTests.swift:8-25.
  2. K-70 The install snippet (SPM, from: "1.0.0"). Source: GPKit: README.md:29-38.
  3. K-11 Data 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.
  4. G-10 On 11 July 2026 the catalog assigned number 100000, to SARAMAGO. Source: gpconf: README.md:98-99; docs/BRAND.md:59.
  5. G-11 The five-digit range ended at 69,999 (CelesTrak). Source: gpconf: docs/BRAND.md:59; docs/RESEARCH.md:32-36.
  6. G-12 Space-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.
  7. K-24 Catalog 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.
  8. K-02 A 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.
  9. K-12 One record type whatever the format; "the TLE is a way in, not the model". Source: GPKit: ElementSet.swift:3-4; README.md:64-65.
  10. K-27 Values are ExactDecimal. Applied defaults are recorded in defaulted. Source: GPKit: ExactDecimal.swift:1-6; ElementSet.swift:54-76.
  11. K-25 Refusals 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.
  12. K-04 Platforms: macOS 13, iOS 16, tvOS 16, visionOS 1. Linux is built in CI. Source: GPKit: Package.swift:9; .github/workflows/ci.yml:16.
  13. K-05 No watchOS, because Int is 32 bits on most watches. Source: GPKit: Package.swift:7-8.
  14. K-14 No 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.
  15. K-03 GPKit makes no network request of its own. Source: GPKit: README.md:14-15.
  16. K-90 Merged 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).
  17. K-91 watchOS 9 and later: GPKit builds for watchOS (arm64 and arm64_32), in CI on every push; what doesn't fit a 32-bit Int is counted in Int64. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07): CHANGELOG.md:61, :65-68; README.md:19-21; .github/workflows/ci.yml:208.
  18. K-92 GPKit 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.
  19. K-96 Sun (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) and PassVisibility (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.
  20. K-97 Earth-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.
  21. K-95 Propagator.states: many satellites at once, in parallel, each result the one state(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).
  22. K-98 Rotator 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.
  23. K-99 OEM.write: an ephemeris as a CCSDS Orbit Ephemeris Message (OEM 2.0, KVN), SGP4's states in TEME and UTC; in CI Python's oem package reads it back. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07): CHANGELOG.md:15-18; docs/public-api.swift:149.
  24. K-100 CloseApproach.screen (and gpkit 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.
  25. K-94 The gpkit command-line tool: read, tle, passes, where, track, rotator, oem and approaches; 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.
  26. K-93 pygpkit, GPKit for Python (import pygpkit; not the unrelated gpkit on 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.
  27. K-101 GPKitSources, 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.
  28. K-102 DocC 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/.
  29. K-06 The GP Passes app is built on GPKit. Source: Passes: Modules/Package.swift:20.
  30. K-40 SGP4 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.
  31. K-07 Licence MIT, © 2026 NEOGY LLC. Source: GPKit: LICENSE:1-3.
  32. 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-4 on main at 6d6ce58.
  33. K-29 The 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.
  34. K-23 Alpha-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.
  35. K-71b The Reading example (ElementSets.read, elementSets, refusals, the printed refusal). Source: GPKit: README.md:42-62.
  36. K-44 68 tests in 14 suites; the README examples are compiled by tests. Source: GPKit: grep of @Test and @Suite; ReadmeTests.swift:5-6.
  37. K-43 python-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.
  38. K-42 Skyfield 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; test PassTests.swift:168-248.
  39. K-83 gpconf 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.txt on main at 6d6ce58 (PR #1, merged 2026-11-05T00:42:49Z; first read at 216459a).