Packages/MoonriseKit/Tests/SkyEngineTests/EclipsePathTests.swift
56 lines · 2785 bytes
1import Foundation
2import Testing
3@testable import SkyEngine
4
5/// Distance in km between two points on a sphere.
6private func km(_ a: (lat: Double, lon: Double), _ b: (lat: Double, lon: Double)) -> Double {
7 let r = Double.pi / 180
8 let h = pow(sin((b.lat - a.lat) * r / 2), 2) + cos(a.lat * r) * cos(b.lat * r) * pow(sin((b.lon - a.lon) * r / 2), 2)
9 return 2 * 6371 * asin(h.squareRoot())
10}
11
12@Test func totalEclipse2027Path() throws {
13 // NASA (Espenak): greatest eclipse 2027-08-02 10:07 UT at 25.5° N, 33.2° E,
14 // totality 6 m 23 s, path 258 km wide; the path crosses southern Spain and Luxor.
15 let path = try #require(EclipsePath.compute(near: Date(timeIntervalSince1970: 1_817_201_220)))
16 #expect(path.kind == "Total")
17 #expect(abs(path.greatest.lat - 25.5) < 0.3 && abs(path.greatest.lon - 33.2) < 0.3)
18 let duration = try #require(path.centralDuration)
19 #expect(abs(duration - 383) < 5)
20 #expect(path.points.count > 100)
21
22 func nearest(_ place: (lat: Double, lon: Double)) -> Double {
23 path.points.map { km($0.center, place) }.min()!
24 }
25 #expect(nearest((25.69, 32.64)) < 40) // Luxor, close to the centre line
26 #expect(nearest((36.01, -5.60)) < 120) // Tarifa, inside the path
27
28 let middle = path.points.min { abs($0.date.timeIntervalSince(path.peak)) < abs($1.date.timeIntervalSince(path.peak)) }!
29 #expect(abs(middle.widthKm - 258) < 20)
30}
31
32@Test func noPathForPartialEclipse() {
33 // 21 September 2025 was partial only.
34 #expect(EclipsePath.compute(near: Date(timeIntervalSince1970: 1_758_483_660)) == nil)
35}
36
37@Test func localCircumstances2027() throws {
38 let peak = Date(timeIntervalSince1970: 1_817_201_220)
39 let luxor = try #require(LocalEclipse.at(Observer(latitude: 25.69, longitude: 32.64), near: peak))
40 #expect(luxor.kind == "Total")
41 let begin = try #require(luxor.centralBegin), end = try #require(luxor.centralEnd)
42 #expect(end.timeIntervalSince(begin) > 360)
43 let madrid = try #require(LocalEclipse.at(Observer(latitude: 40.42, longitude: -3.70), near: peak))
44 #expect(madrid.kind == "Partial" && madrid.centralBegin == nil)
45 #expect(madrid.obscuration > 0.8 && madrid.obscuration < 0.95)
46 #expect(LocalEclipse.at(Observer(latitude: 41.88, longitude: -87.63), near: peak) == nil) // Chicago: night
47}
48
49@Test func moonMatchesHorizonsFrame() {
50 // Artemis I from JPL Horizons (geocentric, J2000 ecliptic, km) at 12:47 UTC on 21 November 2022,
51 // ten minutes before its closest approach, about 130 km above the Moon.
52 let craft = SIMD3<Double>(-337_632.7, -172_437.2, 9_689.0)
53 let moon = SkyEngine.geocentricMoonEcliptic(at: Date(timeIntervalSince1970: 1_669_034_820))
54 let d = ((craft - moon) * (craft - moon)).sum().squareRoot()
55 #expect(d < 3_000)
56}