Vulcan
Aerospace Engineering Utilities Built on Janus
Loading...
Searching...
No Matches
FrameLocal.hpp
Go to the documentation of this file.
1// Vulcan Local Frames
2// Convenience functions for local tangent plane reference frames
3#pragma once
4
7
8#include <janus/math/Trig.hpp>
9
10namespace vulcan {
11
12// =============================================================================
13// Local Geodetic Horizon Frames
14// =============================================================================
15
29template <typename Scalar>
30CoordinateFrame<Scalar> local_ned(Scalar lon, Scalar lat_gd) {
31 return CoordinateFrame<Scalar>::ned(lon, lat_gd);
32}
33
47template <typename Scalar>
48CoordinateFrame<Scalar> local_enu(Scalar lon, Scalar lat_gd) {
49 return CoordinateFrame<Scalar>::enu(lon, lat_gd);
50}
51
52// =============================================================================
53// Local Geocentric Horizon Frame
54// =============================================================================
55
74template <typename Scalar>
75CoordinateFrame<Scalar> local_geocentric(Scalar lon, Scalar lat_gc) {
76 Scalar sin_lat = janus::sin(lat_gc);
77 Scalar cos_lat = janus::cos(lat_gc);
78 Scalar sin_lon = janus::sin(lon);
79 Scalar cos_lon = janus::cos(lon);
80
81 // North: perpendicular to radial direction, in meridian plane, pointing
82 // north
83 // = -sin(lat_gc)*cos(lon), -sin(lat_gc)*sin(lon), cos(lat_gc)
84 Vec3<Scalar> north;
85 north << -sin_lat * cos_lon, -sin_lat * sin_lon, cos_lat;
86
87 // East: tangent to parallel of latitude
88 // = -sin(lon), cos(lon), 0
89 Vec3<Scalar> east;
90 east << -sin_lon, cos_lon, Scalar(0);
91
92 // Down: toward Earth center (negative of radial unit vector)
93 // = -cos(lat_gc)*cos(lon), -cos(lat_gc)*sin(lon), -sin(lat_gc)
94 Vec3<Scalar> down;
95 down << -cos_lat * cos_lon, -cos_lat * sin_lon, -sin_lat;
96
97 return CoordinateFrame<Scalar>(north, east, down, Vec3<Scalar>::Zero());
98}
99
108template <typename Scalar>
109CoordinateFrame<Scalar> local_geocentric_at(const Vec3<Scalar> &r_ecef) {
111 return local_geocentric(geo.lon, geo.lat_gc);
112}
113
123template <typename Scalar>
124CoordinateFrame<Scalar>
125local_ned_at(const Vec3<Scalar> &r_ecef,
126 const EarthModel &m = EarthModel::WGS84()) {
127 LLA<Scalar> lla = ecef_to_lla(r_ecef, m);
128 return local_ned(lla.lon, lla.lat);
129}
130
140template <typename Scalar>
141CoordinateFrame<Scalar>
142local_enu_at(const Vec3<Scalar> &r_ecef,
143 const EarthModel &m = EarthModel::WGS84()) {
144 LLA<Scalar> lla = ecef_to_lla(r_ecef, m);
145 return local_enu(lla.lon, lla.lat);
146}
147
148// =============================================================================
149// Rail Launch Frame
150// =============================================================================
151
169template <typename Scalar>
170CoordinateFrame<Scalar>
171local_rail(const LLA<Scalar> &lla_origin, const Scalar &azimuth,
172 const Scalar &elevation,
173 [[maybe_unused]] const EarthModel &m = EarthModel::WGS84()) {
174 // Start with NED axes at origin
175 const Scalar sin_lat = janus::sin(lla_origin.lat);
176 const Scalar cos_lat = janus::cos(lla_origin.lat);
177 const Scalar sin_lon = janus::sin(lla_origin.lon);
178 const Scalar cos_lon = janus::cos(lla_origin.lon);
179
180 // NED basis vectors in ECEF
181 Vec3<Scalar> north;
182 north << -sin_lat * cos_lon, -sin_lat * sin_lon, cos_lat;
183
184 Vec3<Scalar> east;
185 east << -sin_lon, cos_lon, Scalar(0);
186
187 Vec3<Scalar> down;
188 down << -cos_lat * cos_lon, -cos_lat * sin_lon, -sin_lat;
189
190 // Trig for azimuth and elevation
191 const Scalar sin_az = janus::sin(azimuth);
192 const Scalar cos_az = janus::cos(azimuth);
193 const Scalar sin_el = janus::sin(elevation);
194 const Scalar cos_el = janus::cos(elevation);
195
196 // Step 1: Rotate by azimuth in horizontal plane
197 // Horizontal forward direction = cos(az)*North + sin(az)*East
198 Vec3<Scalar> horiz_fwd = cos_az * north + sin_az * east;
199
200 // Horizontal right direction = -sin(az)*North + cos(az)*East
201 Vec3<Scalar> horiz_right = -sin_az * north + cos_az * east;
202
203 // Step 2: Pitch up by elevation
204 // Rail X-axis: cos(el)*horiz_fwd - sin(el)*down (up is -down)
205 Vec3<Scalar> rail_x = cos_el * horiz_fwd - sin_el * down;
206
207 // Rail Y-axis: remains horiz_right (perpendicular to pitch rotation)
208 Vec3<Scalar> rail_y = horiz_right;
209
210 // Rail Z-axis: sin(el)*horiz_fwd + cos(el)*down
211 Vec3<Scalar> rail_z = sin_el * horiz_fwd + cos_el * down;
212
213 return CoordinateFrame<Scalar>(rail_x, rail_y, rail_z,
214 Vec3<Scalar>::Zero());
215}
216
227template <typename Scalar>
228CoordinateFrame<Scalar>
229local_rail_at(const Vec3<Scalar> &r_ecef, const Scalar &azimuth,
230 const Scalar &elevation,
231 const EarthModel &m = EarthModel::WGS84()) {
232 LLA<Scalar> lla = ecef_to_lla(r_ecef, m);
233 return local_rail(lla, azimuth, elevation, m);
234}
235
236// =============================================================================
237// CDA Frame (Cross-range, Down-range, Altitude) — Trajectory-Relative
238// =============================================================================
239
256template <typename Scalar>
257CoordinateFrame<Scalar>
258local_cda(const LLA<Scalar> &lla_origin, const Scalar &bearing,
259 [[maybe_unused]] const EarthModel &m = EarthModel::WGS84()) {
260 // Start with local ENU frame at origin
261 const Scalar sin_lat = janus::sin(lla_origin.lat);
262 const Scalar cos_lat = janus::cos(lla_origin.lat);
263 const Scalar sin_lon = janus::sin(lla_origin.lon);
264 const Scalar cos_lon = janus::cos(lla_origin.lon);
265
266 // ENU basis vectors in ECEF
267 Vec3<Scalar> east;
268 east << -sin_lon, cos_lon, Scalar(0);
269
270 Vec3<Scalar> north;
271 north << -sin_lat * cos_lon, -sin_lat * sin_lon, cos_lat;
272
273 Vec3<Scalar> up;
274 up << cos_lat * cos_lon, cos_lat * sin_lon, sin_lat;
275
276 // Rotate about Up axis by bearing angle
277 // Down-range = cos(bearing) * North + sin(bearing) * East
278 // Cross-range = -sin(bearing) * North + cos(bearing) * East (right-hand)
279 const Scalar sin_b = janus::sin(bearing);
280 const Scalar cos_b = janus::cos(bearing);
281
282 Vec3<Scalar> downrange = cos_b * north + sin_b * east;
283 Vec3<Scalar> crossrange = -sin_b * north + cos_b * east;
284
285 // CDA frame: D (downrange) as X, C (crossrange) as Y, A (altitude/up) as Z
286 return CoordinateFrame<Scalar>(downrange, crossrange, up,
287 Vec3<Scalar>::Zero());
288}
289
299template <typename Scalar>
300CoordinateFrame<Scalar>
301local_cda_at(const Vec3<Scalar> &r_ecef, const Scalar &bearing,
302 const EarthModel &m = EarthModel::WGS84()) {
303 LLA<Scalar> lla = ecef_to_lla(r_ecef, m);
304 return local_cda(lla, bearing, m);
305}
306
307// =============================================================================
308// CDA Coordinate Conversions
309// =============================================================================
310
322template <typename Scalar>
323Vec3<Scalar> ecef_to_cda(const Vec3<Scalar> &r_ecef, const LLA<Scalar> &lla_ref,
324 const Scalar &bearing,
325 const EarthModel &m = EarthModel::WGS84()) {
326 // Get CDA frame at reference point
327 CoordinateFrame<Scalar> cda_frame = local_cda(lla_ref, bearing, m);
328
329 // Get reference point in ECEF
330 Vec3<Scalar> r_ref = lla_to_ecef(lla_ref, m);
331
332 // Compute offset vector
333 Vec3<Scalar> delta = r_ecef - r_ref;
334
335 // Project onto CDA axes (frame axes are columns of rotation matrix)
336 // CDA = R^T * delta where R = [downrange | crossrange | up]
337 Vec3<Scalar> cda;
338 cda(0) = delta.dot(cda_frame.x_axis); // Down-range
339 cda(1) = delta.dot(cda_frame.y_axis); // Cross-range
340 cda(2) = delta.dot(cda_frame.z_axis); // Altitude
341
342 return cda;
343}
344
355template <typename Scalar>
356Vec3<Scalar> cda_to_ecef(const Vec3<Scalar> &cda, const LLA<Scalar> &lla_ref,
357 const Scalar &bearing,
358 const EarthModel &m = EarthModel::WGS84()) {
359 // Get CDA frame at reference point
360 CoordinateFrame<Scalar> cda_frame = local_cda(lla_ref, bearing, m);
361
362 // Get reference point in ECEF
363 Vec3<Scalar> r_ref = lla_to_ecef(lla_ref, m);
364
365 // Convert CDA to ECEF offset
366 // delta = R * cda where R = [downrange | crossrange | up]
367 Vec3<Scalar> delta = cda(0) * cda_frame.x_axis + cda(1) * cda_frame.y_axis +
368 cda(2) * cda_frame.z_axis;
369
370 return r_ref + delta;
371}
372
373} // namespace vulcan
Definition Aerodynamics.hpp:11
CoordinateFrame< Scalar > local_enu(Scalar lon, Scalar lat_gd)
Definition FrameLocal.hpp:48
CoordinateFrame< Scalar > local_geocentric(Scalar lon, Scalar lat_gc)
Definition FrameLocal.hpp:75
CoordinateFrame< Scalar > local_enu_at(const Vec3< Scalar > &r_ecef, const EarthModel &m=EarthModel::WGS84())
Definition FrameLocal.hpp:142
CoordinateFrame< Scalar > local_ned(Scalar lon, Scalar lat_gd)
Definition FrameLocal.hpp:30
CoordinateFrame< Scalar > local_rail(const LLA< Scalar > &lla_origin, const Scalar &azimuth, const Scalar &elevation, const EarthModel &m=EarthModel::WGS84())
Definition FrameLocal.hpp:171
Vec3< Scalar > lla_to_ecef(const LLA< Scalar > &lla, const EarthModel &m=EarthModel::WGS84())
Definition Geodetic.hpp:152
Vec3< Scalar > ecef_to_cda(const Vec3< Scalar > &r_ecef, const LLA< Scalar > &lla_ref, const Scalar &bearing, const EarthModel &m=EarthModel::WGS84())
Definition FrameLocal.hpp:323
Spherical< Scalar > ecef_to_spherical(const Vec3< Scalar > &r)
Definition Geodetic.hpp:190
CoordinateFrame< Scalar > local_rail_at(const Vec3< Scalar > &r_ecef, const Scalar &azimuth, const Scalar &elevation, const EarthModel &m=EarthModel::WGS84())
Definition FrameLocal.hpp:229
LLA< Scalar > ecef_to_lla(const Vec3< Scalar > &r, const EarthModel &m=EarthModel::WGS84())
Definition Geodetic.hpp:78
Vec3< Scalar > cda_to_ecef(const Vec3< Scalar > &cda, const LLA< Scalar > &lla_ref, const Scalar &bearing, const EarthModel &m=EarthModel::WGS84())
Definition FrameLocal.hpp:356
CoordinateFrame< Scalar > local_cda_at(const Vec3< Scalar > &r_ecef, const Scalar &bearing, const EarthModel &m=EarthModel::WGS84())
Definition FrameLocal.hpp:301
CoordinateFrame< Scalar > local_cda(const LLA< Scalar > &lla_origin, const Scalar &bearing, const EarthModel &m=EarthModel::WGS84())
Definition FrameLocal.hpp:258
CoordinateFrame< Scalar > local_geocentric_at(const Vec3< Scalar > &r_ecef)
Definition FrameLocal.hpp:109
CoordinateFrame< Scalar > local_ned_at(const Vec3< Scalar > &r_ecef, const EarthModel &m=EarthModel::WGS84())
Definition FrameLocal.hpp:125
Definition FramePrimitives.hpp:44
Vec3< Scalar > y_axis
Unit Y basis vector in ECEF.
Definition FramePrimitives.hpp:46
static CoordinateFrame enu(Scalar lon, Scalar lat)
Definition FramePrimitives.hpp:301
Vec3< Scalar > z_axis
Unit Z basis vector in ECEF.
Definition FramePrimitives.hpp:47
static CoordinateFrame ned(Scalar lon, Scalar lat)
Definition FramePrimitives.hpp:269
Vec3< Scalar > x_axis
Unit X basis vector in ECEF.
Definition FramePrimitives.hpp:45
Definition EarthModel.hpp:27
static constexpr EarthModel WGS84()
WGS84 reference ellipsoid (most common for GPS/navigation).
Definition EarthModel.hpp:45
Definition Geodetic.hpp:27
Scalar lon
Longitude [rad], positive East, range [-π, π].
Definition Geodetic.hpp:28
Scalar lat
Geodetic latitude [rad], range [-π/2, π/2].
Definition Geodetic.hpp:29
Definition Geodetic.hpp:49
Scalar lon
Longitude [rad], positive East, range [-π, π].
Definition Geodetic.hpp:50
Scalar lat_gc
Geocentric latitude [rad], range [-π/2, π/2].
Definition Geodetic.hpp:51