siderust-cpp 0.8.0
Header-only C++ wrapper for siderust
Loading...
Searching...
No Matches
spherical.hpp
Go to the documentation of this file.
1#pragma once
2
9#include "../astro_context.hpp"
10#include "../centers.hpp"
11#include "../constants.hpp"
12#include "../frames.hpp"
13#include "../time.hpp"
14#include "geodetic.hpp"
15
16#include <qtty/qtty.hpp>
17
18#include "detail/stream.hpp"
19
20#include <cmath>
21#include <ostream>
22#include <type_traits>
23
24// Forward-declare cartesian Position to avoid circular include.
25namespace siderust {
26namespace cartesian {
27template <typename C, typename F, typename U> struct Position;
28}
29} // namespace siderust
30
31namespace siderust {
32namespace spherical {
33
50template <typename F> struct Direction {
51 static_assert(frames::is_frame_v<F>, "F must be a valid frame tag");
52
53private:
54 qtty::Degree azimuth_;
55 qtty::Degree polar_;
56
57public:
58 Direction() : azimuth_(qtty::Degree(0)), polar_(qtty::Degree(0)) {}
59
60 Direction(qtty::Degree azimuth, qtty::Degree polar) : azimuth_(azimuth), polar_(polar) {}
61
65 static constexpr const char *frame_name() { return frames::FrameTraits<F>::name(); }
67
70 template <typename F_ = F, std::enable_if_t<frames::has_ra_dec_v<F_>, int> = 0>
71 qtty::Degree ra() const {
72 return azimuth_;
73 }
74
75 template <typename F_ = F, std::enable_if_t<frames::has_ra_dec_v<F_>, int> = 0>
76 qtty::Degree dec() const {
77 return polar_;
78 }
80
83 template <typename F_ = F, std::enable_if_t<frames::has_az_alt_v<F_>, int> = 0>
84 qtty::Degree az() const {
85 return azimuth_;
86 }
87
88 template <typename F_ = F, std::enable_if_t<frames::has_az_alt_v<F_>, int> = 0>
89 qtty::Degree al() const {
90 return polar_;
91 }
92
93 template <typename F_ = F, std::enable_if_t<frames::has_az_alt_v<F_>, int> = 0>
94 qtty::Degree alt() const {
95 return polar_;
96 }
97
98 template <typename F_ = F, std::enable_if_t<frames::has_az_alt_v<F_>, int> = 0>
99 qtty::Degree altitude() const {
100 return polar_;
101 }
103
106 template <typename F_ = F, std::enable_if_t<frames::has_lon_lat_v<F_>, int> = 0>
107 qtty::Degree lon() const {
108 return azimuth_;
109 }
110
111 template <typename F_ = F, std::enable_if_t<frames::has_lon_lat_v<F_>, int> = 0>
112 qtty::Degree lat() const {
113 return polar_;
114 }
115
116 template <typename F_ = F, std::enable_if_t<frames::has_lon_lat_v<F_>, int> = 0>
117 qtty::Degree longitude() const {
118 return azimuth_;
119 }
120
121 template <typename F_ = F, std::enable_if_t<frames::has_lon_lat_v<F_>, int> = 0>
122 qtty::Degree latitude() const {
123 return polar_;
124 }
126
129 siderust_spherical_dir_t to_c() const { return {polar_.value(), azimuth_.value(), frame_id()}; }
130
132 return Direction(qtty::Degree(c.azimuth_deg), qtty::Degree(c.polar_deg));
133 }
135
144 qtty::Degree angular_separation(const Direction &other) const {
145 constexpr double DEG2RAD = constants::pi / 180.0;
146 constexpr double RAD2DEG = 180.0 / constants::pi;
147 const double az1 = azimuth_.value() * DEG2RAD;
148 const double po1 = polar_.value() * DEG2RAD;
149 const double az2 = other.azimuth_.value() * DEG2RAD;
150 const double po2 = other.polar_.value() * DEG2RAD;
151 const double x =
152 std::cos(po1) * std::sin(po2) - std::sin(po1) * std::cos(po2) * std::cos(az2 - az1);
153 const double y = std::cos(po2) * std::sin(az2 - az1);
154 const double z =
155 std::sin(po1) * std::sin(po2) + std::cos(po1) * std::cos(po2) * std::cos(az2 - az1);
156 return qtty::Degree(std::atan2(std::sqrt(x * x + y * y), z) * RAD2DEG);
157 }
158
167 template <typename Target>
168 std::enable_if_t<frames::has_frame_transform_v<F, Target>, Direction<Target>>
169 to_frame(const Time<TT, JD> &jd) const {
170 if constexpr (std::is_same_v<F, Target>) {
171 return Direction<Target>(azimuth_, polar_);
172 } else {
175 polar_.value(), azimuth_.value(), frames::FrameTraits<F>::ffi_id,
177 "Direction::to_frame");
179 }
180 }
181
185 template <typename Target>
186 std::enable_if_t<frames::has_frame_transform_v<F, Target>, Direction<Target>>
188 if constexpr (std::is_same_v<F, Target>) {
189 return Direction<Target>(azimuth_, polar_);
190 } else {
194 polar_.value(), azimuth_.value(), frames::FrameTraits<F>::ffi_id,
196 "Direction::to_frame_with");
198 }
199 }
200
204 template <typename Target>
205 auto to(const Time<TT, JD> &jd) const -> decltype(this->template to_frame<Target>(jd)) {
206 return to_frame<Target>(jd);
207 }
208
216 constexpr double DEG2RAD = constants::pi / 180.0;
217 const double az = azimuth_.value() * DEG2RAD;
218 const double po = polar_.value() * DEG2RAD;
219 const double cp = std::cos(po);
220 return cartesian::Direction<F>(std::cos(az) * cp, std::sin(az) * cp, std::sin(po));
221 }
222
226 template <typename F_ = F>
227 std::enable_if_t<frames::has_horizontal_transform_v<F_>, Direction<frames::Horizontal>>
230 check_status(siderust_spherical_dir_to_horizontal(polar_.value(), azimuth_.value(),
232 observer.to_c(), &out),
233 "Direction::to_horizontal");
235 }
236
240 template <typename F_ = F>
241 std::enable_if_t<frames::has_horizontal_transform_v<F_>, Direction<frames::Horizontal>>
243 const AstroContext &ctx) const {
247 polar_.value(), azimuth_.value(), frames::FrameTraits<F>::ffi_id, jd.value(),
248 jd.value(), observer.to_c(), fctx.get(), &out),
249 "Direction::to_horizontal_with");
251 }
252
264 template <typename F_ = F>
265 std::enable_if_t<frames::has_horizontal_transform_v<F_>, Direction<frames::Horizontal>>
267 const Geodetic &observer) const {
270 polar_.value(), azimuth_.value(), frames::FrameTraits<F>::ffi_id,
271 jd_tt.value(), jd_ut1.value(), observer.to_c(), &out),
272 "Direction::to_horizontal_precise");
274 }
275};
276
287template <typename C, typename F, typename U = qtty::Meter> struct Position {
288 static_assert(frames::is_frame_v<F>, "F must be a valid frame tag");
289 static_assert(centers::is_center_v<C>, "C must be a valid center tag");
290
291private:
292 qtty::Degree azimuth_;
293 qtty::Degree polar_;
294 U dist_;
295
296public:
297 Position(qtty::Degree azimuth, qtty::Degree polar, U distance)
298 : azimuth_(azimuth), polar_(polar), dist_(distance) {}
299
301 : azimuth_(dir.azimuth()), polar_(dir.polar()), dist_(distance) {}
302
304 Direction<F> direction() const { return Direction<F>(azimuth_, polar_); }
305
308 template <typename F_ = F, std::enable_if_t<frames::has_ra_dec_v<F_>, int> = 0>
309 qtty::Degree ra() const {
310 return azimuth_;
311 }
312
313 template <typename F_ = F, std::enable_if_t<frames::has_ra_dec_v<F_>, int> = 0>
314 qtty::Degree dec() const {
315 return polar_;
316 }
317
318 template <typename F_ = F, std::enable_if_t<frames::has_az_alt_v<F_>, int> = 0>
319 qtty::Degree az() const {
320 return azimuth_;
321 }
322
323 template <typename F_ = F, std::enable_if_t<frames::has_az_alt_v<F_>, int> = 0>
324 qtty::Degree al() const {
325 return polar_;
326 }
327
328 template <typename F_ = F, std::enable_if_t<frames::has_az_alt_v<F_>, int> = 0>
329 qtty::Degree alt() const {
330 return polar_;
331 }
332
333 template <typename F_ = F, std::enable_if_t<frames::has_lon_lat_v<F_>, int> = 0>
334 qtty::Degree lon() const {
335 return azimuth_;
336 }
337
338 template <typename F_ = F, std::enable_if_t<frames::has_lon_lat_v<F_>, int> = 0>
339 qtty::Degree lat() const {
340 return polar_;
341 }
343
346
347 U distance() const { return dist_; }
352
364 template <typename Target>
365 std::enable_if_t<frames::has_frame_transform_v<F, Target>, Position<C, Target, U>>
366 to_frame(const Time<TT, JD> &jd) const;
367
368 template <typename Target>
369 std::enable_if_t<frames::has_frame_transform_v<F, Target>, Position<C, Target, U>>
371
375 template <typename Target>
376 auto to(const Time<TT, JD> &jd) const -> decltype(this->template to_frame<Target>(jd)) {
377 return to_frame<Target>(jd);
378 }
379
389 qtty::Degree angular_separation(const Position &other) const {
390 return direction().angular_separation(other.direction());
391 }
392
393 U distance_to(const Position &other) const {
394 using std::sqrt;
395 // Values in underlying unit (e.g. meters)
396 const double r = dist_.value();
397 const double s = other.dist_.value();
398
399 // convert degrees to radians
400 constexpr double DEG2RAD = constants::pi / 180.0;
401 const double a1 = azimuth_.value() * DEG2RAD;
402 const double p1 = polar_.value() * DEG2RAD;
403 const double a2 = other.azimuth_.value() * DEG2RAD;
404 const double p2 = other.polar_.value() * DEG2RAD;
405
406 // dot product of unit direction vectors (spherical -> cartesian)
407 double cos_p1 = std::cos(p1);
408 double cos_p2 = std::cos(p2);
409 double dot = cos_p1 * cos_p2 * std::cos(a1 - a2) + std::sin(p1) * std::sin(p2);
410 if (dot > 1.0)
411 dot = 1.0;
412 if (dot < -1.0)
413 dot = -1.0;
414
415 double d = std::sqrt(r * r + s * s - 2.0 * r * s * dot);
416 return U(d);
417 }
418};
419
420// ============================================================================
421// Stream operators
422// ============================================================================
423
427template <typename F, std::enable_if_t<frames::has_ra_dec_v<F>, int> = 0>
428inline std::ostream &operator<<(std::ostream &os, const Direction<F> &dir) {
429 coordinates::detail::write_frame<F>(os);
430 return os << " (ra=" << dir.ra() << ", dec=" << dir.dec() << ')';
431}
432
436template <typename F, std::enable_if_t<frames::has_az_alt_v<F>, int> = 0>
437inline std::ostream &operator<<(std::ostream &os, const Direction<F> &dir) {
438 coordinates::detail::write_frame<F>(os);
439 return os << " (az=" << dir.az() << ", alt=" << dir.alt() << ')';
440}
441
445template <typename F, std::enable_if_t<frames::has_lon_lat_v<F>, int> = 0>
446inline std::ostream &operator<<(std::ostream &os, const Direction<F> &dir) {
447 coordinates::detail::write_frame<F>(os);
448 return os << " (lon=" << dir.lon() << ", lat=" << dir.lat() << ')';
449}
450
454template <typename C, typename F, typename U, std::enable_if_t<frames::has_ra_dec_v<F>, int> = 0>
455inline std::ostream &operator<<(std::ostream &os, const Position<C, F, U> &pos) {
456 coordinates::detail::write_center_frame<C, F>(os);
457 return os << " (ra=" << pos.ra() << ", dec=" << pos.dec() << ", r=" << pos.distance() << ')';
458}
459
463template <typename C, typename F, typename U, std::enable_if_t<frames::has_az_alt_v<F>, int> = 0>
464inline std::ostream &operator<<(std::ostream &os, const Position<C, F, U> &pos) {
465 coordinates::detail::write_center_frame<C, F>(os);
466 return os << " (az=" << pos.az() << ", alt=" << pos.alt() << ", r=" << pos.distance() << ')';
467}
468
472template <typename C, typename F, typename U, std::enable_if_t<frames::has_lon_lat_v<F>, int> = 0>
473inline std::ostream &operator<<(std::ostream &os, const Position<C, F, U> &pos) {
474 coordinates::detail::write_center_frame<C, F>(os);
475 return os << " (lon=" << pos.lon() << ", lat=" << pos.lat() << ", r=" << pos.distance() << ')';
476}
477
478} // namespace spherical
479} // namespace siderust
constexpr double pi
Circle constant pi (radians per half-turn).
Definition constants.hpp:12
std::ostream & operator<<(std::ostream &os, const Direction< F > &dir)
Stream operator for Direction with RA/Dec frames.
tempoch::EncodedTime< Scale, Format > Time
Definition time.hpp:36
void check_status(siderust_status_t status, const char *operation)
Definition ffi_core.hpp:111
Shared helpers for human-readable coordinate stream output.
Geodetic position (WGS84 ellipsoid).
Definition geodetic.hpp:29
A unit-vector direction in Cartesian form, compile-time frame-tagged.
Definition cartesian.hpp:41
A 3D Cartesian position, compile-time tagged by center, frame, unit.
SFINAE helper: every frame tag must provide these static members.
Definition frames.hpp:27
A direction on the celestial sphere, compile-time tagged by frame.
Definition spherical.hpp:50
qtty::Degree lon() const
std::enable_if_t< frames::has_horizontal_transform_v< F_ >, Direction< frames::Horizontal > > to_horizontal_with(const Time< TT, JD > &jd, const Geodetic &observer, const AstroContext &ctx) const
Transform to the horizontal frame with an explicit context.
qtty::Degree al() const
Definition spherical.hpp:89
qtty::Degree lat() const
static constexpr siderust_frame_t frame_id()
Definition spherical.hpp:64
qtty::Degree alt() const
Definition spherical.hpp:94
Direction(qtty::Degree azimuth, qtty::Degree polar)
Definition spherical.hpp:60
qtty::Degree az() const
Definition spherical.hpp:84
qtty::Degree latitude() const
cartesian::Direction< F > to_cartesian() const
Convert this spherical direction to a Cartesian unit vector.
static constexpr const char * frame_name()
Definition spherical.hpp:65
qtty::Degree altitude() const
Definition spherical.hpp:99
std::enable_if_t< frames::has_frame_transform_v< F, Target >, Direction< Target > > to_frame_with(const Time< TT, JD > &jd, const AstroContext &ctx) const
Transform to a different reference frame with an explicit context.
qtty::Degree angular_separation(const Direction &other) const
Angular separation between this direction and another.
auto to(const Time< TT, JD > &jd) const -> decltype(this->template to_frame< Target >(jd))
Shorthand: .to<Target>(jd) (calls to_frame).
siderust_spherical_dir_t to_c() const
std::enable_if_t< frames::has_horizontal_transform_v< F_ >, Direction< frames::Horizontal > > to_horizontal(const Time< TT, JD > &jd, const Geodetic &observer) const
Transform to the horizontal (alt-az) frame.
std::enable_if_t< frames::has_horizontal_transform_v< F_ >, Direction< frames::Horizontal > > to_horizontal_precise(const Time< TT, JD > &jd_tt, const Time< UT1, JD > &jd_ut1, const Geodetic &observer) const
High-precision horizontal transform with explicit TT and UT1 epochs.
std::enable_if_t< frames::has_frame_transform_v< F, Target >, Direction< Target > > to_frame(const Time< TT, JD > &jd) const
Transform to a different reference frame.
qtty::Degree dec() const
Definition spherical.hpp:76
qtty::Degree ra() const
Definition spherical.hpp:71
qtty::Degree longitude() const
static Direction from_c(const siderust_spherical_dir_t &c)
A spherical position (direction + distance), compile-time tagged.
cartesian::Position< C, F, U > to_cartesian() const
Convert this spherical position to a cartesian Position<C,F,U>.
static constexpr siderust_center_t center_id()
Direction< F > direction() const
Extract the direction component.
static constexpr siderust_frame_t frame_id()
qtty::Degree ra() const
qtty::Degree alt() const
Position(qtty::Degree azimuth, qtty::Degree polar, U distance)
Position(const Direction< F > &dir, U distance)
qtty::Degree az() const
auto to(const Time< TT, JD > &jd) const -> decltype(this->template to_frame< Target >(jd))
Shorthand: .to<Target>(jd) (calls to_frame).
qtty::Degree dec() const
qtty::Degree lon() const
std::enable_if_t< frames::has_frame_transform_v< F, Target >, Position< C, Target, U > > to_frame_with(const Time< TT, JD > &jd, const AstroContext &ctx) const
U distance_to(const Position &other) const
qtty::Degree angular_separation(const Position &other) const
Angular separation between this position's direction and another's.
std::enable_if_t< frames::has_frame_transform_v< F, Target >, Position< C, Target, U > > to_frame(const Time< TT, JD > &jd) const
Transform this position to a different reference frame (same center).
qtty::Degree al() const
qtty::Degree lat() const