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/SkyRendererTests/SkyRenderTests.swift

59d401dca1a327705632d1e2e20f00f807dee828
moonrise/Packages/MoonriseKit/Tests/SkyRendererTests/SkyRenderTests.swift history · blame · raw

91 lines · 3941 bytes

7 symbols in this file
 1import Foundation
 2import SkyEngine
 3import SwiftUI
 4import Testing
 5@testable import SkyRenderer
 6
 7let chicago = Observer(latitude: 41.8781, longitude: -87.6298)
 8let evening = Date(timeIntervalSince1970: 1_792_202_400) // 2026-10-17T02:00:00Z, 21:00 CDT
 9
10@Test func catalogLoads() {
11    #expect(Catalog.stars.count > 9_000)
12    #expect(Catalog.stars.contains { $0.hr == 2491 && $0.magnitude < -1 }) // Sirius
13}
14
15@Test func sceneHasSaturnUpAndMarsDown() throws {
16    let scene = try SkyScene(date: evening, observer: chicago)
17    let saturn = try #require(scene.bodies.first { $0.body == .saturn })
18    let mars = try #require(scene.bodies.first { $0.body == .mars })
19    #expect(saturn.position.altitude > 30)
20    #expect(mars.position.altitude < 0)
21}
22
23/// Renders the sky to a PNG and reports timings. Set SKY_RENDER_OUT to keep the image.
24@MainActor
25@Test func renderStaticSky() throws {
26    let clock = ContinuousClock()
27    var scene: SkyScene!
28    let build = try clock.measure { scene = try SkyScene(date: evening, observer: chicago) }
29
30    let renderer = ImageRenderer(content: SkyView(scene: scene).frame(width: 393, height: 852))
31    renderer.scale = 3
32    var image: CGImage?
33    let render = clock.measure { image = renderer.cgImage }
34    let cg = try #require(image)
35
36    print("scene: \(scene.stars.count) stars, build \(build); render \(cg.width)x\(cg.height) in \(render)")
37
38    if let out = ProcessInfo.processInfo.environment["SKY_RENDER_OUT"] {
39        let url = URL(fileURLWithPath: out)
40        let dest = try #require(CGImageDestinationCreateWithURL(url as CFURL, "public.png" as CFString, 1, nil))
41        CGImageDestinationAddImage(dest, cg, nil)
42        #expect(CGImageDestinationFinalize(dest))
43    }
44}
45
46@Test func tapOnSaturnFindsSaturnAndNearbyStarsFindTheirConstellation() throws {
47    let scene = try SkyScene(date: evening, observer: chicago)
48    let p = Projection(centerAzimuth: 180, centerAltitude: 45, fieldOfView: 100, size: CGSize(width: 393, height: 852))
49    let saturn = try #require(scene.bodies.first { $0.body == .saturn })
50    let pt = try #require(p.point(saturn.position))
51    #expect(scene.object(at: CGPoint(x: pt.x + 5, y: pt.y), projection: p) == .body(.saturn))
52
53    // Altair (HR 7557) is in Aquila's figure.
54    let altair = try #require(scene.starsByHR[7557])
55    let a = try #require(p.point(altair))
56    #expect(scene.object(at: a, projection: p) == .constellation("Aql"))
57}
58
59@Test func starBinsStayCloseToEachStar() {
60    var used = Set<Int>()
61    for star in Catalog.stars where star.magnitude <= 6.5 {
62        let bin = StarBin(magnitude: star.magnitude, colorIndex: star.colorIndex)
63        #expect(abs(bin.magnitude - star.magnitude) <= 0.25, "HR \(star.hr)")
64        let tint = min(max((star.colorIndex + 0.3) / 2, 0), 1)
65        #expect(abs(bin.tint - tint) <= 1.0 / 6 + 1e-9, "HR \(star.hr)")
66        used.insert(bin.index)
67    }
68    #expect(used.allSatisfy { (0..<StarBin.count).contains($0) })
69}
70
71@MainActor
72@Test func nightStyleRenders() throws {
73    let scene = try SkyScene(date: evening, observer: chicago)
74    let renderer = ImageRenderer(content: SkyView(scene: scene, style: .night).frame(width: 393, height: 852))
75    #expect(renderer.cgImage != nil)
76}
77
78@Test func skyCulturesUseCatalogueStars() {
79    let cultures = Catalog.skyCultures
80    #expect(cultures.count == 10)
81    let known = Set(Catalog.stars.map(\.hr))
82    for c in cultures {
83        #expect(!c.constellations.isEmpty, "\(c.id)")
84        #expect(c.licence.contains("BY-SA") || c.licence.contains("BY SA"), "\(c.id)")
85        for f in c.constellations {
86            #expect(f.lines.allSatisfy { $0.count > 1 && $0.allSatisfy(known.contains) }, "\(c.id) \(f.name)")
87        }
88    }
89    // The Norse Wolf's Mouth is the Hyades and includes Aldebaran (HR 1457).
90    #expect(cultures.first { $0.id == "norse" }?.constellations.contains { $0.name == "Wolf's Mouth" && $0.lines.joined().contains(1457) } == true)
91}