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/SkyRenderer/SkyScene.swift

04b2b1d914788057bcd20945b876847386eb8f65
moonrise/Packages/MoonriseKit/Sources/SkyRenderer/SkyScene.swift history · blame · raw

110 lines · 4305 bytes

9 symbols in this file
  1import Foundation
  2import SkyEngine
  3
  4public struct Star: Sendable {
  5    public var hr: Int
  6    public var ra: Double
  7    public var dec: Double
  8    public var magnitude: Double
  9    public var colorIndex: Double
 10    /// Proper motion in arcseconds per year; `pmRA` already includes cos(dec).
 11    public var pmRA: Double = 0
 12    public var pmDec: Double = 0
 13
 14    /// J2000 coordinates moved by proper motion to `date` (linear, which is close enough over a few thousand years).
 15    public func position(at date: Date) -> (ra: Double, dec: Double) {
 16        let years = date.timeIntervalSince(SkyEngine.j2000) / (365.25 * 86_400)
 17        let dec = self.dec + pmDec * years / 3600
 18        let ra = self.ra + pmRA * years / 3600 / max(cos(self.dec * .pi / 180), 1e-6)
 19        return (ra, dec)
 20    }
 21}
 22
 23public struct Constellation: Sendable, Decodable {
 24    public var id: String
 25    /// Polylines of HR numbers.
 26    public var lines: [[Int]]
 27}
 28
 29/// Another culture's figures over the same stars, from Stellarium's sky cultures (CC BY-SA).
 30public struct SkyCulture: Sendable, Decodable, Identifiable {
 31    public struct Figure: Sendable, Decodable {
 32        public var name: String
 33        public var native: String?
 34        /// Polylines of HR numbers.
 35        public var lines: [[Int]]
 36    }
 37
 38    public var id: String
 39    public var name: String
 40    public var region: String
 41    public var licence: String
 42    public var source: URL
 43    public var constellations: [Figure]
 44}
 45
 46public enum Catalog {
 47    /// Yale Bright Star Catalogue, 5th ed. (VizieR V/50): HR, RA and Dec (J2000, degrees), V, B−V,
 48    /// proper motion in RA (× cos dec) and Dec (arcsec/yr).
 49    public static let stars: [Star] = {
 50        let url = Bundle.module.url(forResource: "bsc5", withExtension: "json")!
 51        let rows = try! JSONDecoder().decode([[Double]].self, from: Data(contentsOf: url))
 52        return rows.map { Star(hr: Int($0[0]), ra: $0[1], dec: $0[2], magnitude: $0[3], colorIndex: $0[4], pmRA: $0[5], pmDec: $0[6]) }
 53    }()
 54
 55    /// Stars by HR number.
 56    public static let starsByHR: [Int: Star] = Dictionary(uniqueKeysWithValues: stars.map { ($0.hr, $0) })
 57
 58    /// Built by scripts/make-skycultures.py.
 59    public static let skyCultures: [SkyCulture] = {
 60        let url = Bundle.module.url(forResource: "skycultures", withExtension: "json")!
 61        return try! JSONDecoder().decode([SkyCulture].self, from: Data(contentsOf: url))
 62    }()
 63
 64    public static let constellations: [Constellation] = {
 65        let url = Bundle.module.url(forResource: "constellations", withExtension: "json")!
 66        return try! JSONDecoder().decode([Constellation].self, from: Data(contentsOf: url))
 67    }()
 68}
 69
 70public struct PlacedStar: Sendable {
 71    public var star: Star
 72    public var position: Horizontal
 73}
 74
 75public struct PlacedBody: Sendable {
 76    public var body: Body
 77    public var position: Horizontal
 78    public var magnitude: Double
 79    public var phaseFraction: Double
 80}
 81
 82/// Everything above (and just below) the horizon for one place and time.
 83public struct SkyScene: Sendable {
 84    public var date: Date
 85    public var observer: Observer
 86    public var stars: [PlacedStar]
 87    public var starsByHR: [Int: Horizontal]
 88    public var bodies: [PlacedBody]
 89    /// Moon's elongation east of the sun, degrees (0 new, 180 full).
 90    public var moonPhase: Double
 91
 92    public init(date: Date, observer: Observer, magnitudeLimit: Double = 6.5) throws {
 93        self.date = date
 94        self.observer = observer
 95        let visible = Catalog.stars.filter { $0.magnitude <= magnitudeLimit }
 96        let positions = SkyEngine.horizontal(j2000: visible.map { $0.position(at: date) }, at: date, from: observer)
 97        stars = zip(visible, positions).map { PlacedStar(star: $0, position: $1) }
 98        starsByHR = Dictionary(uniqueKeysWithValues: stars.map { ($0.star.hr, $0.position) })
 99        bodies = try ([.moon] + Body.planets).map { body in
100            let i = try SkyEngine.illumination(body, at: date)
101            return PlacedBody(
102                body: body,
103                position: try SkyEngine.horizontal(body, at: date, from: observer),
104                magnitude: i.magnitude,
105                phaseFraction: i.phaseFraction
106            )
107        }
108        moonPhase = try SkyEngine.moonPhase(at: date)
109    }
110}