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/Sources/SkyEngine/Tonight.swift

61 lines · 2660 bytes

2 symbols in this file
 1import Foundation
 2
 3public struct PlanetTonight: Sendable {
 4    public var body: Body
 5    public var magnitude: Double
 6    public var now: Horizontal
 7    /// Highest point during the night, if it gets above 10°.
 8    public var best: (date: Date, position: Horizontal)?
 9    public var rise: Date?
10    public var set: Date?
11}
12
13public struct Tonight: Sendable {
14    /// Darkness window: from now if it is already dark, else from the next sunset.
15    public var start: Date
16    public var end: Date
17    public var sunset: Date?
18    public var sunrise: Date?
19    public var moonIllumination: Double
20    public var moonPhase: Double
21    public var nextQuarter: (quarter: SkyEngine.MoonQuarter, date: Date)
22    /// Naked-eye planets, those visible tonight first, brightest first.
23    public var planets: [PlanetTonight]
24
25    public init(at date: Date, from observer: Observer) throws {
26        let sunNow = try SkyEngine.horizontal(.sun, at: date, from: observer, refraction: true)
27        let sunToday = SkyEngine.riseSet(.sun, after: date, from: observer)
28        if sunNow.altitude < -0.8 {
29            start = date
30            sunset = nil
31        } else {
32            sunset = sunToday.set
33            start = sunToday.set ?? date
34        }
35        sunrise = SkyEngine.riseSet(.sun, after: start, from: observer).rise
36        end = sunrise ?? start.addingTimeInterval(12 * 3600)
37
38        moonIllumination = try SkyEngine.illumination(.moon, at: date).phaseFraction
39        moonPhase = try SkyEngine.moonPhase(at: date)
40        nextQuarter = try SkyEngine.nextMoonQuarter(after: date)
41
42        let samples = stride(from: start.timeIntervalSince1970, through: end.timeIntervalSince1970, by: 900).map(Date.init(timeIntervalSince1970:))
43        planets = try [Body.mercury, .venus, .mars, .jupiter, .saturn].map { body in
44            var best: (Date, Horizontal)?
45            for s in samples {
46                let h = try SkyEngine.horizontal(body, at: s, from: observer, refraction: true)
47                if h.altitude > 10, h.altitude > best?.1.altitude ?? -90 { best = (s, h) }
48            }
49            let rs = SkyEngine.riseSet(body, after: date, from: observer)
50            return PlanetTonight(
51                body: body,
52                magnitude: try SkyEngine.illumination(body, at: date).magnitude,
53                now: try SkyEngine.horizontal(body, at: date, from: observer, refraction: true),
54                best: best.map { (date: $0.0, position: $0.1) },
55                rise: rs.rise,
56                set: rs.set
57            )
58        }
59        .sorted { ($0.best != nil ? 0 : 1, $0.magnitude) < ($1.best != nil ? 0 : 1, $1.magnitude) }
60    }
61}