Getting started
Install
Swift Package Manager, in Package.swiftK-70:
dependencies: [
.package(url: "https://github.com/hneogy/GPKit.git", from: "1.0.0"),
],
targets: [
.target(name: "YourTarget", dependencies: ["GPKit"]),
]
In Xcode: File → Add Package Dependencies, and the same URL. Versions follow semantic versioning.K-70
Requirements for 1.0.0: macOS 13, iOS 16, tvOS 16 or visionOS 1, or Linux. 1.0.0 doesn't build for watchOS.K-04K-05
GPKit's main, not yet in a release, adds watchOS 9, Windows, Android and WebAssembly.K-91K-92
1. Read element sets
Give GPKit the bytes your provider served (Data, [UInt8], any collection of bytes, or a String) and say which
format they're inK-71b:
import GPKit
let file = try ElementSets.read(bytes, as: .json) // .csv, .json, .xml, .kvn or .tle
for set in file.elementSets {
set.catalogNumber // 25544: an Int, whatever its width
set.objectID // "1998-067A", or nil when the source has none
set.epoch // 1998-11-20T06:49:59.999808, kept as integers
set.meanMotion // 16.05064833, the digits the source wrote
set.meanMotion.double // the nearest Double, for computing
}
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)
}
- A record that can't be read is a
Refusalin its place, and the rest of the file is still read.K-25 readthrows only when the whole input can't be what you said it is: not UTF-8, empty, JSON or XML that doesn't parse, or a file that ends inside a record.K-25K-26- A provider's "no data" answer, such as CelesTrak's
No GP data found, isn't an error. The file has no entries, andproviderMessageholds the answer.K-73 - For a TLE source known to write wrong check digits:
ElementSets.read(bytes, as: .tle, checksum: .ignore).K-21
2. Propagate
let propagator = try Propagator(set) // refuses an element set that is not for SGP4
let state = try propagator.state(at: time) // time is an Epoch, UTC
state.position // kilometres from the Earth's centre, TEME
state.velocity // kilometres per second
try propagator.state(minutesFromEpoch: 90) // or by minutes from the element set's epoch
let beneath = try propagator.position(at: time) // the point of the WGS-84 ellipsoid beneath the satellite
beneath.latitude; beneath.longitude // degrees, north and east positive
beneath.altitude // kilometres above that point
K-74 Distances are in kilometres and speeds in kilometres per second. The one exception is an Observer's height,
which is in metres.K-15
Time. An Epoch is UTC. Epoch(date) and epoch.date convert to and from Foundation's Date, to the
microsecond. Without Foundation, use Epoch(unixTime:) and unixTime. advanced(by:) and seconds(since:) work in
seconds.K-74
3. Find passes
let home = Observer(latitude: 40.0, longitude: -75.0, height: 100) // degrees, degrees, and a height in metres
let passes = propagator.passes(over: home, from: now, for: .seconds(86_400)) // the next day, above the horizon
for pass in passes {
pass.rise?.time // nil when the satellite was already up at the start
pass.culmination.look.elevation // degrees, at its highest
pass.set?.look.azimuth // degrees clockwise from north; nil when still up at the end
}
passes.failure // nil when the whole day was searched
K-72 Add above: 10 for a minimum elevation in degrees.K-10K-53K-77
4. Look angles and Doppler
let look = try propagator.look(from: home, at: time)
look.azimuth; look.elevation // degrees
look.range; look.rangeRate // km, and km/s (positive while receding)
look.received(from: 437_000_000) // what to tune a receiver to, for a downlink on 437 MHz
look.transmit(toBeReceivedAt: 145_900_000) // what to tune a transmitter to, for an uplink on 145.9 MHz
5. Write a TLE
let lines = try TLE.write(set)
lines.line1 // "1 A0000U ...": five digits below 100000, Alpha-5 from 100000 to 339999
lines.text // the two lines, or three with the name
K-29 A catalog number above 339999 can't go in a TLE, so write refuses it. Keep that record as OMM.K-29K-23
Building and testing GPKit itself
swift build
swift test
With the Command Line Tools and no Xcode, the test build needs the testing plugin path. See the README.K-76
API overview → · How it works → · GitHub →
Sources
K-70The install snippet (SPM, from: "1.0.0"). Source: GPKit:README.md:29-38.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-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-71bThe Reading example (ElementSets.read,elementSets,refusals, the printed refusal). Source: GPKit:README.md:42-62.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-26A file cut short is refused whole. Source: GPKit:README.md:85-87; testOMMTests.swift:120.K-73A provider's "No GP data found" is not an error: no entries,providerMessageholds it. Source: GPKit:README.md:98-99.K-21The checksum is verified by default;.ignoreskips only the checksum. Source: GPKit:TLE.swift:8-34;README.md:93-96; testTLETests.swift:77-99.K-74The Propagating example and Epoch conversions (state(at:),state(minutesFromEpoch:),position(at:),Epoch(date),unixTime,advanced(by:),seconds(since:)). Source: GPKit:README.md:159-186.K-15Kilometres everywhere except an Observer's height, which is in metres. Source: GPKit:README.md:173-174.K-72The Passes example (Observerwith height in metres,passes(over:from:for:), rise/culmination/set). Source: GPKit:README.md:190-207.K-10The public API (signatures). Source: GPKit:docs/public-api.swift(generated bytools/public-api.sh).K-53No refraction; angles are geometric. Source: GPKit:README.md:216-217.K-77The /docs/ lines not in the README compile and run:look(from:at:),look.azimuth,.elevation,.range,.rangeRate, andpasses(over:from:for:above: 10). Source: 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-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.K-75The Doppler lineslook.received(from: 437_000_000)andlook.transmit(toBeReceivedAt: 145_900_000). Source: GPKit:README.md:205-206.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-76Building and testing:swift build,swift test, and the Command Line Tools plugin path. Source: GPKit:README.md:267-281.