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}