krz/moonrise

Night-sky companion for iPhone, Apple Watch and Vision Pro: what's up tonight, the Moon, planets, launches and missions, and a Library of the solar system. SwiftUI app with a Cloudflare Workers backend. astronomy cloudflare-workers ios night-sky space swiftui visionos watchos

Packages/MoonriseKit/Tests/SkyEngineTests/SGP4Tests.swift

62 lines · 3280 bytes

 1import Foundation
 2import Testing
 3@testable import SkyEngine
 4
 5// Vallado's SGP4 verification case for satellite 00005 (SGP4-VER.TLE / tcppver.out):
 6// 1 00005U 58002B   00179.78495062  .00000023  00000-0  28098-4 0  4753
 7// 2 00005  34.2682 348.7242 1859667 331.7664  19.3264 10.82419157413667
 8// Epoch 2000 day 179.78495062 = 2000-06-27T18:50:19.733568Z.
 9let vanguard = OrbitalElements(name: "00005", norad: 5, epoch: "2000-06-27T18:50:19.733568",
10                               meanMotion: 10.82419157, eccentricity: 0.1859667, inclination: 34.2682,
11                               raan: 348.7242, argPerigee: 331.7664, meanAnomaly: 19.3264, bstar: 0.28098e-4)
12
13func dist(_ a: SIMD3<Double>, _ b: SIMD3<Double>) -> Double { let d = a - b; return (d * d).sum().squareRoot() }
14
15@Test(arguments: [
16    (0.0, SIMD3(7022.46529266, -1400.08296755, 0.03995155), SIMD3(1.893841015, 6.405893759, 4.534807250)),
17    (360.0, SIMD3(-7154.03120202, -3783.17682504, -3536.19412294), SIMD3(4.741887409, -4.151817765, -2.093935425)),
18    (720.0, SIMD3(-7134.59340119, 6531.68641334, 3260.27186483), SIMD3(-4.113793027, -2.911922039, -2.557327851)),
19])
20func matchesValladoVerification(_ t: Double, _ r: SIMD3<Double>, _ v: SIMD3<Double>) throws {
21    let s = try SGP4(vanguard)
22    let out = try #require(s.propagate(minutes: t))
23    #expect(dist(out.r, r) < 0.01, "t=\(t): r off by \(dist(out.r, r)) km")
24    #expect(dist(out.v, v) < 1e-5, "t=\(t): v off by \(dist(out.v, v)) km/s")
25}
26
27@Test func epochParsesFractionalSeconds() throws {
28    let d = try #require(vanguard.epochDate)
29    #expect(abs(d.timeIntervalSince1970 - 962_131_819.733568) < 1e-3)
30}
31
32@Test func issIsInLowOrbit() throws {
33    // CelesTrak GP for the ISS, 2026-10-18.
34    let iss = OrbitalElements(name: "ISS (ZARYA)", norad: 25544, epoch: "2026-10-18T03:28:15.076416",
35                              meanMotion: 15.48918166, eccentricity: 0.000678, inclination: 51.6328,
36                              raan: 51.6259, argPerigee: 277.7856, meanAnomaly: 82.2363, bstar: 0.00014456)
37    let s = try SGP4(iss)
38    let r = try #require(s.propagate(minutes: 60)?.r)
39    let altitude = (r * r).sum().squareRoot() - 6378.135
40    #expect(altitude > 380 && altitude < 450, "\(altitude) km")
41}
42
43@Test func gmstAtJ2000() {
44    // GMST at 2000-01-01 12:00 UT1 is 280.46061837 degrees.
45    let g = SkyEngine.gmst(Date(timeIntervalSince1970: 946_728_000)) * 180 / .pi
46    #expect(abs(g - 280.46061837) < 1e-4)
47}
48
49@Test func issPassesOverChicago() throws {
50    let iss = OrbitalElements(name: "ISS (ZARYA)", norad: 25544, epoch: "2026-10-18T03:28:15.076416",
51                              meanMotion: 15.48918166, eccentricity: 0.000678, inclination: 51.6328,
52                              raan: 51.6259, argPerigee: 277.7856, meanAnomaly: 82.2363, bstar: 0.00014456)
53    let start = try #require(iss.epochDate)
54    let passes = SkyEngine.passes(of: iss, from: start, hours: 72, observer: chicago)
55    // The ISS (51.6° inclination) passes over 42° N several times a day.
56    #expect(passes.count >= 6, "\(passes.count) passes")
57    for p in passes {
58        #expect(p.maxAltitude >= 10 && p.maxAltitude <= 90)
59        let minutes = p.set.timeIntervalSince(p.rise) / 60
60        #expect(minutes > 2 && minutes < 12, "\(minutes) min")
61    }
62}