Satellite Prediction Library
SatelliteKit
is a library, written in Swift, implementing the SGP4/SDP4 earth-orbiting satellite
propagation algorithms first published in the
SpaceTrack Report #3
and later refined by Vallado et al in
Revisiting Spacetrack Report #3.
The code of this library is derived from Orekit which implements
the above published algorithms as a small part of it's extensive capabilities.
Test output from SatelliteKit
agrees, to meaninglessly high precision, with Orekit
test output and the test output in the above published paper [1].
[1] "Vallado, David A.; Paul Crawford; Richard Hujsak; T. S. Kelso, (August 2006), Revisiting Spacetrack Report #3".
Some people will be surprised by some of my source code comment format; it is a style I inherited from a systems programming language I used long ago and it is really not appropriate for publicly released code in the modern age (especially since Swift has markup built in).
Also note that there is extensive use of Unicode characters in property names and other places. This attempts to match, as much as is reasonable, the mathematical notation and Greek characters usage in the original 1980 Spacetrack Report.
At the end of the README. Lastest change: Version/Tag 1.2.0 -- 2024 Jan 08
The Elements
structure is initialized from the three lines of elements in a traditional TLE set.
Some sources of TLEs provide no first line (which would contain the object's informal name) and,
in that case, it is OK to pass a null String
into the initializer.
public init(_ line0: String, _ line1: String, _ line2: String) throws
The public properties that are exposed from in the Elements
structure are:
public let commonName: String // line zero name (if any) [eg: ISS (ZARYA)]
public let noradIndex: UInt // The satellite number [eg: 25544]
public let launchName: String // International designation [eg: 1998-067A]
public let tβ: Double // the TLE t=0 epoch time (days since 1950)
public let eβ: Double // TLE .. eccentricity
public let iβ: Double // TLE .. inclination (radians).
public let Οβ: Double // Argument of perigee (radians).
public let Ξ©β: Double // Right Ascension of Ascending node (radians).
public let Mβ: Double // Mean anomaly (radians).
public var nβ: Double = 0.0 // Mean motion (radians/min) << [un'Kozai'd]
public var aβ: Double = 0.0 // semi-major axis (Eα΅£) << [un'Kozai'd]
public let ephemType: Int // Type of ephemeris.
public let tleClass: String // Classification (U for unclassified).
public let tleNumber: Int // Element number.
public let revNumber: Int // Revolution number at epoch.
Note that the operation to "un Kozai" the element data is performed inside the initialization because both SGP4 and SDP4 need that adjustment.
The initializer will throw an exception if the numeric parsing of the element data fails, however, it will not do so if the record checksum fails. More complete correctness of the element record can be verified by:
public func formatOK(_ line1: String, _ line2: String) -> Bool
which will return true
if the lines are 69 characters long, format is valid, and checksums are good.
Note that line0
doesn't take part in the check so is omitted for this function, and that formatOK
will
emit explicit errors into the log.
There has been concern for some time that the three line element sets will become limited, not least of all because they only allow 5 digits for a object's unique NORAD numeric identifier. It has been proposed to provide other, less constricted, data formats. More information on this move will be found at A New Way to Obtain GP Data (aka TLEs)
SatelliteKit
has been changed to allow the ingestion of GP data in a JSON form .. for example, given JSON
data, this would decode an array of Elements
structures (I'm not catching errors in the example, but you should):
let jsonDecoder = JSONDecoder()
jsonDecoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Micros)
let tleArray = try jsonDecoder.decode([Elements].self, from: jsonData)
print(Satellite(withTLE: tleArray[0]).debugDescription())
print(Satellite(withTLE: tleArray[1]).debugDescription())
print(Satellite(withTLE: tleArray[2]).debugDescription())
The Elements
structure also implements debugDescription
which will generate this formatted String
ββ[elements : 0.66 days old]]ββββββββββββββββββββββββββββββββββββββββββ
β ISS (ZARYA) 25544 = 1998-067A rev#:09857 tle#:0999
β tβ: 2018-02-08 22:51:49 +0000 +24876.95265046 days after 1950
β
β inc: 51.6426Β° aop: 86.7895Β° mot: 15.53899203 (rev/day)
β raan: 297.9871Β° anom: 100.1959Β° ecc: 0.0003401
β drag: +3.2659e-05
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Having obtained the Elements
for a satellite (a struct
which holds only a description of the orbital
elements), it is used to initialize a Satellite
struct to manage the propagation of the object's
position and velocity as time is varied from the epochal tβ=0 of the element set.
Whether the object requires the "deep space" propagator, or not, is determined within the Satellite
initialization.
The Satellite
initializers are:
public init(_: String, _: String, _: String) // three TLE lines ..
public init(elements: Elements) // an Elements struct ..
The Satellite
struct offers some public properties and some public functions.
The properties provide some naming information and a "grab bag" directory for whatever you want.
public let tle: Elements // make TLE accessible
public let commonName: String // "COSMOS .."
public let noradIdent: String // "21332"
public let tβDays1950: Double // days since 1950
public var e: Double { return propagator.e } //### these vary slowly over time ..
public var i: Double { return propagator.i } //###
public var Ο: Double { return propagator.Ο } //###
public var Ξ©: Double { return propagator.Ξ© } //###
public var extraInfo = [String: AnyObject](https://raw.github.com/gavineadie/SatelliteKit/main/) // the "grab bag" dictionary ..
The functions accept a time argument, either minutes after the satellite's TLE epoch, or Julian Days, and provide celestial postion (Kilometers) and velocity (Kms/sec) state vectors as output.
public func position(minsAfterEpoch: Double) -> Vector
public func velocity(minsAfterEpoch: Double) -> Vector
public func position(julianDays: Double) -> Vector
public func velocity(julianDays: Double) -> Vector
This is a simple invocation of the above:
do {
let elements = try Elements("ISS (ZARYA)",
"1 25544U 98067A 18039.95265046 .00001678 00000-0 32659-4 0 9999",
"2 25544 51.6426 297.9871 0003401 86.7895 100.1959 15.54072469 98577")
let sat = Satellite(elements)
print(sat.debugDescription())
let posInKms = sat.position(minsAfterEpoch: 10.0)
} catch {
print(error)
}
The most commonly publically available form of TLE data is a file containing multiple concatenated TLEs.
The String
content of such a file may be processed (records that are empty or start with "#" are dropped then
leading and trailing whitespace is stripped and non-breaking spaces are converted to regular spaces)
and checked for quality (line length is 69 characters and the checksum is good) within SatelliteKit with the function:
public func preProcessTLEs(_: String) -> [(String, String, String)]
preProcessTLEs
consumes a String
of TLE records and returns an array of
(String, String, String)
tuples, one per satellite. The tuple items are the, mildly verified, zeroth, first
and second of one satellite's TLE lines. If the TLEs are the two-line variety, the first member of the
tuple is an empty String
.
Thus, the contents of a TLE file would be mapped to an array of Satellite
by:
let satArray = preProcessTLEs(fileContents).map( { return Satellite($0.0, $0.1, $0.2) } )
A more rigorous quality check can be preformed using:
public func formatOK(_: String, _: String) -> Bool
which checks the format of TLE lines "1" and "2" .. using a regex test, a time consuming action
that is not performed in preProcessTLEs
.
SatelliteKit
can be added to your project using the Swift Package Manager (SPM) by adding
the dependency:
.package(url: "https://github.com/gavineadie/SatelliteKit.git", from: "1.0.0")
and using import SatelliteKit
in code that needs it.
SatelliteKit
has been used for applications on iOS devices (iPhone, iPad and ο£ΏTV),
and Macintosh computers (SwiftUI, AppKit and command line). It has been exposed to the
Windows and Unix Swift enviroment briefly, but not tested rigorously.
Translation from C++ and Java, testing and distribution by Gavin Eadie
version/tag 1.0.0 .. (2019 Jun 14)
version/tag 1.0.8 .. (2019 Oct 03)
version/tag 1.0.9 .. (2019 Oct 03)
move debugDescription()
from the Elements
structure to the Satellite
structure
remove public access to dragCoeff
(it's never used)
version/tag 1.0.16 .. (2020 Jan 27)
version/tag 1.0.20 .. (2020 Feb 26)
eci2top(..)
was corrected.version/tag 1.0.21 .. (2020 Mar 09)
Elements
set in its debugDescription(..)
.version/tag 1.0.22 .. (2020 Apr 25)
.macOS(.v10_12), .iOS(.v9)
version/tag 1.0.23 .. (2020 Jun 04)
version/tag 1.0.24 .. (2020 Jun 04)
Elements
initializer that consumes a JSON version of the new NORAD GP Element Set.launchName
has been expanded from, for example: 98067A
to 1998-067A
.. since this property
is mostly decorative, with no semantic value, this is not treated as an API changeversion/tag 1.0.25 .. (2020 Jun 07)
Elements
initializer.Elements
initializer.version/tag 1.0.26 .. (2020 Jun 30)
version/tag 1.0.27 .. (2021 Jan 30)
Elements
struct accessible from the Satellite struct.version/tag 1.0.27 .. (2021 Mar 05)
noradIndex
) are processed correctly.version/tag 1.0.28 .. (2021 May 14)
version/tag 1.0.30 .. (2022 Feb 17)
version/tag 1.0.31 .. (2022 Feb 19)
Elements
initialization: factored out unKozai()
moved XML parsing to own file
revised the XML unit test
NOTE: API CHANGES (minor version number changed .. backward compatible, for now)**
version/tag 1.1.0 .. (2022 Feb 28)
TLE
struct replaced with Elements
TLE
typealias'd to Elements
(for backward compatibility)debugDescription()
is now a method on Elements
TLEPropagator
class replaced with Propagator
.. (private anyway)noradIndex
can't be negative so made UInt
TimeUtility.swift
Elements.nβΚΉ
removed from public
version/tag 1.1.1 .. (2022 Mar 02)
TLE
struct deprecatedversion/tag 1.1.2 .. (2022 Mar 05)
tleClass == "U"
removed [Starlinks are "C"]version/tag 1.1.3 .. (2022 Oct 10)
OBJECT_ID
can be null)INCLINATION
) being numbers (Celestrak) or strings (Space-Track)version/tag 1.1.4 .. (2023 Jan 07)
version/tag 1.1.5 .. (2023 Aug 26)
1.0.17
changed to WGS-84 constants which caused a divergence between
the Vallado results and the results of this library. That change is reversed,
as it should be, because Vallado's algorithms are built expecting WGS-72 constants.
Now the results from this library and Vallado's agree, again, to a ridiculous extent !version/tag 1.1.6 .. (2023 Sep 27)
version/tag 1.1.7 .. (2023 Nov 06)
MathUtility
.. improve vector dot and cross products (and unit tests) ..ThreeLineElementTests
.. adjust unit tests results for reversion to WGS-72 ..version/tag 1.1.8 .. (2023 Nov 27)
macOS(.v13), .iOS(.v16), .tvOS(.v16), .watchOS(.v9)
..version/tag 1.2.0 .. (2024 Jan 08)
link |
Stars: 23 |
Last commit: 8 weeks ago |
Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics