Vulcan
Aerospace Engineering Utilities Built on Janus
Loading...
Searching...
No Matches
FrameContext.hpp
Go to the documentation of this file.
1// Vulcan Frame Context
2// User-facing API for frame-tree setup and transforms
3#pragma once
4
12
13#include <memory>
14#include <optional>
15#include <string>
16#include <utility>
17#include <vector>
18
19namespace vulcan {
20
21template <typename Scalar> class FrameContext {
22 public:
23 FrameContext() : registry_(FrameRegistry::default_aerospace()) {
24 initialize_storage();
25 }
26
28 : registry_(std::move(registry)) {
29 initialize_storage();
30 }
31
32 // =========================================================================
33 // Frame setup
34 // =========================================================================
35
36 void set_ecef(Scalar rotation_angle) {
37 providers_[FRAME_ECEF.id] =
38 std::make_shared<ECEFProvider<Scalar>>(rotation_angle);
39
41 frames_[FRAME_ECI.id] = CoordinateFrame<Scalar>::eci(rotation_angle);
42 }
43
44 void set_ecef(const EarthRotationModel &model, double t_seconds) {
45 providers_[FRAME_ECEF.id] =
46 std::make_shared<ECEFProvider<Scalar>>(model, t_seconds);
47
49 const Scalar angle = Scalar(model.ecef_to_eci_angle(t_seconds));
50 frames_[FRAME_ECI.id] = CoordinateFrame<Scalar>::eci(angle);
51 }
52
53 void set_ned(Scalar lon, Scalar lat) {
54 auto provider = std::make_shared<NEDProvider<Scalar>>(lon, lat);
55 providers_[FRAME_NED.id] = provider;
56 frames_[FRAME_NED.id] = provider->frame();
57 }
58
59 void set_enu(Scalar lon, Scalar lat) {
60 auto provider = std::make_shared<ENUProvider<Scalar>>(lon, lat);
61 providers_[FRAME_ENU.id] = provider;
62 frames_[FRAME_ENU.id] = provider->frame();
63 }
64
65 void set_geocentric(Scalar lon, Scalar lat_gc) {
66 auto provider =
67 std::make_shared<GeocentricProvider<Scalar>>(lon, lat_gc);
68 providers_[FRAME_GEOCENTRIC.id] = provider;
69 frames_[FRAME_GEOCENTRIC.id] = provider->frame();
70 }
71
72 void set_rail(const LLA<Scalar> &origin, Scalar azimuth, Scalar elevation,
73 const EarthModel &m = EarthModel::WGS84()) {
74 auto provider = std::make_shared<RailProvider<Scalar>>(origin, azimuth,
75 elevation, m);
76 providers_[FRAME_RAIL.id] = provider;
77 frames_[FRAME_RAIL.id] = provider->frame();
78 }
79
80 void set_cda(const LLA<Scalar> &origin, Scalar bearing,
81 const EarthModel &m = EarthModel::WGS84()) {
82 auto provider =
83 std::make_shared<CDAProvider<Scalar>>(origin, bearing, m);
84 providers_[FRAME_CDA.id] = provider;
85 frames_[FRAME_CDA.id] = provider->frame();
86 }
87
88 void set_body_euler(Scalar yaw, Scalar pitch, Scalar roll) {
89 providers_[FRAME_BODY.id] =
90 std::make_shared<BodyProvider<Scalar>>(yaw, pitch, roll);
91
92 if (has_cached_frame(FRAME_NED)) {
93 frames_[FRAME_BODY.id] =
94 body_from_euler(*frames_[FRAME_NED.id], yaw, pitch, roll);
95 } else {
96 frames_[FRAME_BODY.id].reset();
97 }
98 }
99
100 void set_body_quaternion(const janus::Quaternion<Scalar> &q) {
101 providers_[FRAME_BODY.id] = std::make_shared<BodyProvider<Scalar>>(q);
102
103 if (has_cached_frame(FRAME_NED)) {
104 frames_[FRAME_BODY.id] =
105 body_from_quaternion(*frames_[FRAME_NED.id], q);
106 } else {
107 frames_[FRAME_BODY.id].reset();
108 }
109 }
110
111 void set_wind(Scalar alpha, Scalar beta) {
112 providers_[FRAME_WIND.id] =
113 std::make_shared<WindProvider<Scalar>>(alpha, beta);
114 frames_[FRAME_WIND.id].reset();
115 }
116
117 void set_stability(Scalar alpha) {
118 providers_[FRAME_STABILITY.id] =
119 std::make_shared<StabilityProvider<Scalar>>(alpha);
120 frames_[FRAME_STABILITY.id].reset();
121 }
122
125 if (!registry_.has_frame(id)) {
126 throw CoordinateError("Cannot set unregistered frame");
127 }
128 ensure_capacity(id);
129 if (id == FRAME_ECI) {
130 frames_[FRAME_ECI.id] = frame;
131 return;
132 }
133 if (registry_.parent_of(id) != FRAME_ECEF) {
134 throw CoordinateError("set_frame() requires frame parent to be "
135 "ECEF; use set_provider()");
136 }
137
138 providers_[id.id] =
139 std::make_shared<CoordinateFrameProvider<Scalar>>(frame);
140 frames_[id.id] = frame;
141 }
142
144 std::shared_ptr<TransformProvider<Scalar>> provider) {
145 if (!registry_.has_frame(id)) {
146 throw CoordinateError("Cannot set provider for unregistered frame");
147 }
148 if (id == FRAME_ECI) {
149 throw CoordinateError("Root frame cannot have a provider");
150 }
151 if (!provider) {
152 throw CoordinateError("Provider cannot be null");
153 }
154 ensure_capacity(id);
155 providers_[id.id] = std::move(provider);
156 frames_[id.id].reset();
157 }
158
159 FrameID add_frame(const std::string &name, FrameID parent,
160 std::shared_ptr<TransformProvider<Scalar>> provider) {
161 if (!provider) {
162 throw CoordinateError("Provider cannot be null");
163 }
164 FrameID id = registry_.register_frame(name, parent);
165 ensure_capacity(id);
166 providers_[id.id] = std::move(provider);
167 frames_[id.id].reset();
168 return id;
169 }
170
171 // =========================================================================
172 // Transform operations
173 // =========================================================================
174
175 [[nodiscard]] Vec3<Scalar> transform(const Vec3<Scalar> &v, FrameID from,
176 FrameID to) const {
177 assert_registered(from);
178 assert_registered(to);
179 if (from == to) {
180 return v;
181 }
182
183 // Fast path: direct child -> parent
184 if (registry_.parent_of(from) == to) {
185 return provider_for_child(from)->to_parent(v);
186 }
187
188 // Fast path: direct parent -> child
189 if (registry_.parent_of(to) == from) {
190 return provider_for_child(to)->from_parent(v);
191 }
192
193 return chain(from, to).transform_vector(v);
194 }
195
196 [[nodiscard]] Vec3<Scalar> transform_position(const Vec3<Scalar> &pos,
197 FrameID from,
198 FrameID to) const {
199 assert_registered(from);
200 assert_registered(to);
201 if (from == to) {
202 return pos;
203 }
204
205 // Fast path: direct child -> parent
206 if (registry_.parent_of(from) == to) {
207 return provider_for_child(from)->position_to_parent(pos);
208 }
209
210 // Fast path: direct parent -> child
211 if (registry_.parent_of(to) == from) {
212 return provider_for_child(to)->position_from_parent(pos);
213 }
214
215 return chain(from, to).transform_position(pos);
216 }
217
218 [[nodiscard]] TransformChain<Scalar> chain(FrameID from, FrameID to) const {
219 assert_registered(from);
220 assert_registered(to);
221 if (from == to) {
222 FramePath identity_path;
223 identity_path.frames = {from};
224 identity_path.lca = from;
225 identity_path.ascending_count = 0;
226 identity_path.descending_count = 0;
227 return TransformChain<Scalar>(identity_path, {}, {});
228 }
229
230 const FramePath path = registry_.find_path(from, to);
231 if (!path.is_valid()) {
232 throw CoordinateError("No transform path between requested frames");
233 }
234 return build_chain(path);
235 }
236
237 // =========================================================================
238 // Backward-compatible frame access
239 // =========================================================================
240
241 [[nodiscard]] const CoordinateFrame<Scalar> &frame(FrameID id) const {
242 assert_registered(id);
243 if (id.id >= frames_.size() || !frames_[id.id].has_value()) {
244 throw CoordinateError(
245 "Requested frame is not available as CoordinateFrame");
246 }
247 return *frames_[id.id];
248 }
249
250 [[nodiscard]] const FrameRegistry &registry() const { return registry_; }
251
252 private:
253 FrameRegistry registry_;
254 std::vector<std::shared_ptr<TransformProvider<Scalar>>> providers_;
255 std::vector<std::optional<CoordinateFrame<Scalar>>> frames_;
256
257 void initialize_storage() {
258 providers_.resize(registry_.size());
259 frames_.resize(registry_.size());
260
261 // Default aligned inertial/ECEF state at angle = 0.
262 // Guard against custom registries that may not include built-in IDs.
263 if (registry_.has_frame(FRAME_ECEF) &&
264 FRAME_ECEF.id < providers_.size() &&
265 FRAME_ECEF.id < frames_.size()) {
266 providers_[FRAME_ECEF.id] =
267 std::make_shared<ECEFProvider<Scalar>>(Scalar(0));
269 }
270 if (registry_.has_frame(FRAME_ECI) && FRAME_ECI.id < frames_.size()) {
271 frames_[FRAME_ECI.id] = CoordinateFrame<Scalar>::eci(Scalar(0));
272 }
273 }
274
275 void ensure_capacity(FrameID id) {
276 const size_t needed = static_cast<size_t>(id.id + 1);
277 if (providers_.size() < needed) {
278 providers_.resize(needed);
279 }
280 if (frames_.size() < needed) {
281 frames_.resize(needed);
282 }
283 }
284
285 void assert_registered(FrameID id) const {
286 if (!registry_.has_frame(id)) {
287 throw CoordinateError("Frame is not registered in this context");
288 }
289 }
290
291 [[nodiscard]] bool has_cached_frame(FrameID id) const {
292 return id.id < frames_.size() && frames_[id.id].has_value();
293 }
294
295 [[nodiscard]] std::shared_ptr<TransformProvider<Scalar>>
296 provider_for_child(FrameID child) const {
297 if (child.id >= providers_.size() || !providers_[child.id]) {
298 throw CoordinateError("Missing provider for frame edge");
299 }
300 return providers_[child.id];
301 }
302
303 [[nodiscard]] TransformChain<Scalar>
304 build_chain(const FramePath &path) const {
305 std::vector<std::shared_ptr<TransformProvider<Scalar>>> ascending;
306 std::vector<std::shared_ptr<TransformProvider<Scalar>>> descending;
307 ascending.reserve(static_cast<size_t>(path.ascending_count));
308 descending.reserve(static_cast<size_t>(path.descending_count));
309
310 // Ascend source -> LCA. For edge child->parent, provider lives on
311 // child.
312 for (int i = 0; i < path.ascending_count; ++i) {
313 const FrameID child = path.frames[static_cast<size_t>(i)];
314 const FrameID parent = path.frames[static_cast<size_t>(i + 1)];
315 if (registry_.parent_of(child) != parent) {
316 throw CoordinateError(
317 "Invalid path topology while building chain");
318 }
319 ascending.push_back(provider_for_child(child));
320 }
321
322 // Descend LCA -> target. For each parent->child step, use child's
323 // provider.
324 const int start = path.ascending_count + 1;
325 for (int i = start; i < static_cast<int>(path.frames.size()); ++i) {
326 const FrameID parent = path.frames[static_cast<size_t>(i - 1)];
327 const FrameID child = path.frames[static_cast<size_t>(i)];
328 if (registry_.parent_of(child) != parent) {
329 throw CoordinateError(
330 "Invalid path topology while building chain");
331 }
332 descending.push_back(provider_for_child(child));
333 }
334
335 return TransformChain<Scalar>(path, ascending, descending);
336 }
337};
338
339} // namespace vulcan
Exception hierarchy for Vulcan aerospace library.
Coordinate system errors (invalid LLA, singularities).
Definition VulcanError.hpp:53
Vec3< Scalar > transform_position(const Vec3< Scalar > &pos, FrameID from, FrameID to) const
Definition FrameContext.hpp:196
const FrameRegistry & registry() const
Definition FrameContext.hpp:250
void set_geocentric(Scalar lon, Scalar lat_gc)
Definition FrameContext.hpp:65
void set_provider(FrameID id, std::shared_ptr< TransformProvider< Scalar > > provider)
Definition FrameContext.hpp:143
void set_frame(FrameID id, const CoordinateFrame< Scalar > &frame)
Set a frame using CoordinateFrame math (requires parent=ECEF).
Definition FrameContext.hpp:124
void set_ned(Scalar lon, Scalar lat)
Definition FrameContext.hpp:53
void set_body_euler(Scalar yaw, Scalar pitch, Scalar roll)
Definition FrameContext.hpp:88
void set_stability(Scalar alpha)
Definition FrameContext.hpp:117
FrameContext()
Definition FrameContext.hpp:23
TransformChain< Scalar > chain(FrameID from, FrameID to) const
Definition FrameContext.hpp:218
void set_body_quaternion(const janus::Quaternion< Scalar > &q)
Definition FrameContext.hpp:100
FrameID add_frame(const std::string &name, FrameID parent, std::shared_ptr< TransformProvider< Scalar > > provider)
Definition FrameContext.hpp:159
const CoordinateFrame< Scalar > & frame(FrameID id) const
Definition FrameContext.hpp:241
void set_ecef(Scalar rotation_angle)
Definition FrameContext.hpp:36
void set_cda(const LLA< Scalar > &origin, Scalar bearing, const EarthModel &m=EarthModel::WGS84())
Definition FrameContext.hpp:80
void set_wind(Scalar alpha, Scalar beta)
Definition FrameContext.hpp:111
void set_ecef(const EarthRotationModel &model, double t_seconds)
Definition FrameContext.hpp:44
FrameContext(FrameRegistry registry)
Definition FrameContext.hpp:27
void set_enu(Scalar lon, Scalar lat)
Definition FrameContext.hpp:59
Vec3< Scalar > transform(const Vec3< Scalar > &v, FrameID from, FrameID to) const
Definition FrameContext.hpp:175
void set_rail(const LLA< Scalar > &origin, Scalar azimuth, Scalar elevation, const EarthModel &m=EarthModel::WGS84())
Definition FrameContext.hpp:72
Setup-time frame tree registry with LCA path finding.
Definition FrameRegistry.hpp:32
size_t size() const
Definition FrameRegistry.hpp:153
bool has_frame(FrameID id) const
Definition FrameRegistry.hpp:125
Definition TransformChain.hpp:14
Definition Aerodynamics.hpp:11
constexpr FrameID FRAME_GEOCENTRIC
Definition FrameID.hpp:56
CoordinateFrame< Scalar > body_from_euler(const CoordinateFrame< Scalar > &ned, Scalar yaw, Scalar pitch, Scalar roll)
Definition FrameVehicle.hpp:99
constexpr FrameID FRAME_ECI
Definition FrameID.hpp:49
CoordinateFrame< Scalar > body_from_quaternion(const CoordinateFrame< Scalar > &ned, const janus::Quaternion< Scalar > &q_body_to_ned)
Definition FrameVehicle.hpp:29
constexpr FrameID FRAME_CDA
Definition FrameID.hpp:58
constexpr FrameID FRAME_RAIL
Definition FrameID.hpp:57
constexpr FrameID FRAME_WIND
Definition FrameID.hpp:54
constexpr FrameID FRAME_BODY
Definition FrameID.hpp:53
constexpr FrameID FRAME_ECEF
Definition FrameID.hpp:50
constexpr FrameID FRAME_STABILITY
Definition FrameID.hpp:55
constexpr FrameID FRAME_ENU
Definition FrameID.hpp:52
constexpr FrameID FRAME_NED
Definition FrameID.hpp:51
Definition FramePrimitives.hpp:44
static CoordinateFrame eci(Scalar gmst)
Definition FramePrimitives.hpp:243
static CoordinateFrame ecef()
Definition FramePrimitives.hpp:231
Definition EarthModel.hpp:27
static constexpr EarthModel WGS84()
WGS84 reference ellipsoid (most common for GPS/navigation).
Definition EarthModel.hpp:45
Definition EarthModel.hpp:75
virtual double ecef_to_eci_angle(double t_seconds) const
Definition EarthModel.hpp:85
Universal frame identifier.
Definition FrameID.hpp:25
uint32_t id
Definition FrameID.hpp:26
Ordered path between frames from source to target.
Definition FrameRegistry.hpp:15
std::vector< FrameID > frames
Definition FrameRegistry.hpp:16
int descending_count
Definition FrameRegistry.hpp:19
bool is_valid() const
Definition FrameRegistry.hpp:21
int ascending_count
Definition FrameRegistry.hpp:18
FrameID lca
Definition FrameRegistry.hpp:17
Definition Geodetic.hpp:27
Interface for a frame edge transform (child <-> parent).
Definition TransformProvider.hpp:16