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/HitTest.swift

6c4b28384d65e09486e0ad62f2ac9d10401cc80c
moonrise/Packages/MoonriseKit/Sources/SkyRenderer/HitTest.swift history · blame · raw

45 lines · 1730 bytes

 1import CoreGraphics
 2import SkyEngine
 3
 4public enum SkyObject: Sendable, Equatable {
 5    case body(Body)
 6    /// IAU abbreviation.
 7    case constellation(String)
 8    /// Messier number.
 9    case deepSky(Int)
10}
11
12extension SkyScene {
13    /// The object under a tap: the nearest visible sun, moon or planet within `radius` points,
14    /// else the nearest Messier object within 0.6 × `radius` (when `deepSky`),
15    /// else the constellation whose figure has a star nearest the tap.
16    public func object(at point: CGPoint, projection p: Projection, radius: CGFloat = 28, deepSky: Bool = true) -> SkyObject? {
17        func distance(_ h: Horizontal) -> CGFloat? {
18            guard h.altitude > 0, let q = p.point(h) else { return nil }
19            return hypot(q.x - point.x, q.y - point.y)
20        }
21        let bodyHit = bodies
22            .filter { $0.magnitude <= 6 }
23            .compactMap { b in distance(b.position).map { (b.body, $0) } }
24            .filter { $0.1 <= radius }
25            .min { $0.1 < $1.1 }
26        if let bodyHit { return .body(bodyHit.0) }
27
28        if deepSky {
29            let hit = self.deepSky
30                .compactMap { d in distance(d.position).map { (d.object.messier, $0) } }
31                .filter { $0.1 <= radius * 0.6 }
32                .min { $0.1 < $1.1 }
33            if let hit { return .deepSky(hit.0) }
34        }
35
36        var best: (String, CGFloat)?
37        for c in Catalog.constellations {
38            for hr in c.lines.joined() {
39                guard let h = starsByHR[hr], let d = distance(h), d <= radius * 1.5 else { continue }
40                if best == nil || d < best!.1 { best = (c.id, d) }
41            }
42        }
43        return best.map { .constellation($0.0) }
44    }
45}