Skip to content

Commit 75b4219

Browse files
authored
Merge pull request #2447 from inuex35/feature/PseudorangeFactorLever
Add PseudorangeFactorArm and DifferentialPseudorangeFactorArm
2 parents b068bc0 + 8396ac5 commit 75b4219

File tree

4 files changed

+683
-2
lines changed

4 files changed

+683
-2
lines changed

gtsam/navigation/PseudorangeFactor.cpp

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include "PseudorangeFactor.h"
99

10+
#include <limits>
11+
1012
namespace {
1113

1214
/// Speed of light in a vacuum (m/s):
@@ -134,4 +136,145 @@ Vector DifferentialPseudorangeFactor::evaluateError(
134136

135137
return Vector1(error);
136138
}
139+
//***************************************************************************
140+
PseudorangeFactorArm::PseudorangeFactorArm(
141+
const Key ecefTbodyKey, const Key receiverClockBiasKey,
142+
const double measuredPseudorange, const Point3& satellitePosition,
143+
const Point3& leverArm, const double satelliteClockBias,
144+
const SharedNoiseModel& model)
145+
: Base(model, ecefTbodyKey, receiverClockBiasKey),
146+
PseudorangeBase{measuredPseudorange, satellitePosition,
147+
satelliteClockBias},
148+
bL_(leverArm) {}
149+
150+
//***************************************************************************
151+
void PseudorangeFactorArm::print(const std::string& s,
152+
const KeyFormatter& keyFormatter) const {
153+
Base::print(s, keyFormatter);
154+
gtsam::print(pseudorange_, "pseudorange (m): ");
155+
gtsam::print(Vector(satPos_), "sat position (ECEF meters): ");
156+
gtsam::print(satClkBias_, "sat clock bias (s): ");
157+
gtsam::print(Vector(bL_), "lever arm (body frame meters): ");
158+
}
159+
160+
//***************************************************************************
161+
bool PseudorangeFactorArm::equals(const NonlinearFactor& expected,
162+
double tol) const {
163+
const This* e = dynamic_cast<const This*>(&expected);
164+
return e != nullptr && Base::equals(*e, tol) &&
165+
traits<double>::Equals(pseudorange_, e->pseudorange_, tol) &&
166+
traits<Point3>::Equals(satPos_, e->satPos_, tol) &&
167+
traits<double>::Equals(satClkBias_, e->satClkBias_, tol) &&
168+
traits<Point3>::Equals(bL_, e->bL_, tol);
169+
}
170+
171+
//***************************************************************************
172+
Vector PseudorangeFactorArm::evaluateError(
173+
const Pose3& ecef_T_body, const double& receiverClockBias,
174+
OptionalMatrixType H_ecef_T_body,
175+
OptionalMatrixType HreceiverClockBias) const {
176+
// Compute antenna position in the ECEF frame:
177+
const Matrix3 ecef_R_body = ecef_T_body.rotation().matrix();
178+
const Point3 antennaPos = ecef_T_body.translation() + ecef_R_body * bL_;
179+
180+
// Apply pseudorange equation: rho = range + c*[dt_u - dt^s]
181+
const Vector3 position_difference = antennaPos - satPos_;
182+
const double range = position_difference.norm();
183+
const double rho = range + CLIGHT * (receiverClockBias - satClkBias_);
184+
const double error = rho - pseudorange_;
185+
186+
// Compute associated derivatives:
187+
if (H_ecef_T_body) {
188+
H_ecef_T_body->resize(1, 6);
189+
if (range < std::numeric_limits<double>::epsilon()) {
190+
H_ecef_T_body->setZero();
191+
} else {
192+
// u = unit vector from satellite to antenna
193+
const Matrix u = (position_difference / range).transpose(); // 1x3
194+
H_ecef_T_body->block<1, 3>(0, 0) =
195+
u * (-ecef_R_body * skewSymmetric(bL_));
196+
H_ecef_T_body->block<1, 3>(0, 3) = u * ecef_R_body;
197+
}
198+
}
199+
200+
if (HreceiverClockBias) {
201+
*HreceiverClockBias = I_1x1 * CLIGHT;
202+
}
203+
204+
return Vector1(error);
205+
}
206+
207+
//***************************************************************************
208+
DifferentialPseudorangeFactorArm::DifferentialPseudorangeFactorArm(
209+
const Key ecefTbodyKey, const Key receiverClockBiasKey,
210+
const Key differentialCorrectionKey, const double measuredPseudorange,
211+
const Point3& satellitePosition, const Point3& leverArm,
212+
const double satelliteClockBias, const SharedNoiseModel& model)
213+
: Base(model, ecefTbodyKey, receiverClockBiasKey, differentialCorrectionKey),
214+
PseudorangeBase{measuredPseudorange, satellitePosition,
215+
satelliteClockBias},
216+
bL_(leverArm) {}
217+
218+
//***************************************************************************
219+
void DifferentialPseudorangeFactorArm::print(
220+
const std::string& s, const KeyFormatter& keyFormatter) const {
221+
Base::print(s, keyFormatter);
222+
gtsam::print(pseudorange_, "pseudorange (m): ");
223+
gtsam::print(Vector(satPos_), "sat position (ECEF meters): ");
224+
gtsam::print(satClkBias_, "sat clock bias (s): ");
225+
gtsam::print(Vector(bL_), "lever arm (body frame meters): ");
226+
}
227+
228+
//***************************************************************************
229+
bool DifferentialPseudorangeFactorArm::equals(
230+
const NonlinearFactor& expected, double tol) const {
231+
const This* e = dynamic_cast<const This*>(&expected);
232+
return e != nullptr && Base::equals(*e, tol) &&
233+
traits<double>::Equals(pseudorange_, e->pseudorange_, tol) &&
234+
traits<Point3>::Equals(satPos_, e->satPos_, tol) &&
235+
traits<double>::Equals(satClkBias_, e->satClkBias_, tol) &&
236+
traits<Point3>::Equals(bL_, e->bL_, tol);
237+
}
238+
239+
//***************************************************************************
240+
Vector DifferentialPseudorangeFactorArm::evaluateError(
241+
const Pose3& ecef_T_body, const double& receiverClockBias,
242+
const double& differentialCorrection, OptionalMatrixType H_ecef_T_body,
243+
OptionalMatrixType HreceiverClockBias,
244+
OptionalMatrixType HdifferentialCorrection) const {
245+
// Compute antenna position in the ECEF frame:
246+
const Matrix3 ecef_R_body = ecef_T_body.rotation().matrix();
247+
const Point3 antennaPos = ecef_T_body.translation() + ecef_R_body * bL_;
248+
249+
// Apply pseudorange equation: rho = range + c*[dt_u - dt^s]
250+
const Vector3 position_difference = antennaPos - satPos_;
251+
const double range = position_difference.norm();
252+
const double rho = range + CLIGHT * (receiverClockBias - satClkBias_);
253+
const double error = rho - pseudorange_ - differentialCorrection;
254+
255+
// Compute associated derivatives:
256+
if (H_ecef_T_body) {
257+
H_ecef_T_body->resize(1, 6);
258+
if (range < std::numeric_limits<double>::epsilon()) {
259+
H_ecef_T_body->setZero();
260+
} else {
261+
// u = unit vector from satellite to antenna
262+
const Matrix u = (position_difference / range).transpose(); // 1x3
263+
H_ecef_T_body->block<1, 3>(0, 0) =
264+
u * (-ecef_R_body * skewSymmetric(bL_));
265+
H_ecef_T_body->block<1, 3>(0, 3) = u * ecef_R_body;
266+
}
267+
}
268+
269+
if (HreceiverClockBias) {
270+
*HreceiverClockBias = I_1x1 * CLIGHT;
271+
}
272+
273+
if (HdifferentialCorrection) {
274+
*HdifferentialCorrection = -I_1x1;
275+
}
276+
277+
return Vector1(error);
278+
}
279+
137280
} // namespace gtsam

gtsam/navigation/PseudorangeFactor.h

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#pragma once
88

99
#include <gtsam/geometry/Point3.h>
10+
#include <gtsam/geometry/Pose3.h>
1011
#include <gtsam/nonlinear/NoiseModelFactorN.h>
1112
#include <gtsam/nonlinear/NonlinearFactor.h>
1213

@@ -225,4 +226,210 @@ template <>
225226
struct traits<DifferentialPseudorangeFactor>
226227
: public Testable<DifferentialPseudorangeFactor> {};
227228

229+
/**
230+
* GNSS pseudorange factor with lever arm correction.
231+
*
232+
* Like PseudorangeFactor, but uses a Pose3 (position + attitude) as the
233+
* receiver state variable, allowing compensation for a lever arm offset
234+
* between the body frame origin and the GNSS antenna location.
235+
*
236+
* The antenna position is computed as:
237+
* antenna_pos = ecef_T_body.translation() + ecef_R_body * bL_
238+
*
239+
* where bL_ is the lever arm from the body origin to the antenna in the body
240+
* frame. The error model is:
241+
* error = ||antenna_pos - satPos|| + c*(dt_u - dt_s) - pseudorange
242+
*
243+
* @note Both the body pose and satellite position are expected in the ECEF
244+
* frame. Any conversion to/from a local navigation frame (NED/ENU) is
245+
* the caller's responsibility.
246+
*
247+
* @ingroup navigation
248+
*/
249+
class GTSAM_EXPORT PseudorangeFactorArm
250+
: public NoiseModelFactorN<Pose3, double>,
251+
private PseudorangeBase {
252+
private:
253+
typedef NoiseModelFactorN<Pose3, double> Base;
254+
255+
Point3 bL_; ///< Lever arm from body origin to antenna in body frame.
256+
257+
public:
258+
// Provide access to the Matrix& version of evaluateError:
259+
using Base::evaluateError;
260+
261+
/// shorthand for a smart pointer to a factor
262+
typedef std::shared_ptr<PseudorangeFactorArm> shared_ptr;
263+
264+
/// Typedef to this class
265+
typedef PseudorangeFactorArm This;
266+
267+
/** default constructor - only use for serialization */
268+
PseudorangeFactorArm() : PseudorangeBase{0.0, Point3(0, 0, 0), 0.0}, bL_(0, 0, 0) {}
269+
270+
virtual ~PseudorangeFactorArm() = default;
271+
272+
/**
273+
* Construct a PseudorangeFactorArm.
274+
*
275+
* @param ecefTbodyKey Receiver gtsam::Pose3 key (body pose in ECEF frame).
276+
* @param receiverClockBiasKey Receiver clock bias node.
277+
* @param measuredPseudorange Receiver-measured pseudorange in meters.
278+
* @param satellitePosition Satellite ECEF position in meters.
279+
* @param leverArm Translation from body origin to antenna in body frame.
280+
* @param satelliteClockBias Satellite clock bias in seconds.
281+
* @param model 1-D pseudorange noise model.
282+
*/
283+
PseudorangeFactorArm(
284+
Key ecefTbodyKey, Key receiverClockBiasKey,
285+
double measuredPseudorange, const Point3& satellitePosition,
286+
const Point3& leverArm, double satelliteClockBias = 0.0,
287+
const SharedNoiseModel& model = noiseModel::Unit::Create(1));
288+
289+
/// @return a deep copy of this factor
290+
gtsam::NonlinearFactor::shared_ptr clone() const override {
291+
return std::static_pointer_cast<gtsam::NonlinearFactor>(
292+
gtsam::NonlinearFactor::shared_ptr(new This(*this)));
293+
}
294+
295+
/// print
296+
void print(const std::string& s = "", const KeyFormatter& keyFormatter =
297+
DefaultKeyFormatter) const override;
298+
299+
/// equals
300+
bool equals(const NonlinearFactor& expected,
301+
double tol = 1e-9) const override;
302+
303+
/// vector of errors
304+
Vector evaluateError(const Pose3& ecef_T_body,
305+
const double& receiverClockBias,
306+
OptionalMatrixType H_ecef_T_body,
307+
OptionalMatrixType HreceiverClockBias) const override;
308+
309+
/// return the lever arm, a position in the body frame
310+
inline const Point3& leverArm() const { return bL_; }
311+
312+
private:
313+
#if GTSAM_ENABLE_BOOST_SERIALIZATION ///
314+
/// Serialization function
315+
friend class boost::serialization::access;
316+
template <class ARCHIVE>
317+
void serialize(ARCHIVE& ar, const unsigned int /*version*/) {
318+
ar& BOOST_SERIALIZATION_BASE_OBJECT_NVP(PseudorangeFactorArm::Base);
319+
ar& BOOST_SERIALIZATION_NVP(pseudorange_);
320+
ar& BOOST_SERIALIZATION_NVP(satPos_);
321+
ar& BOOST_SERIALIZATION_NVP(satClkBias_);
322+
ar& BOOST_SERIALIZATION_NVP(bL_);
323+
}
324+
#endif
325+
};
326+
327+
/// traits
328+
template <>
329+
struct traits<PseudorangeFactorArm>
330+
: public Testable<PseudorangeFactorArm> {};
331+
332+
/**
333+
* Differentially-corrected pseudorange factor with lever arm correction.
334+
*
335+
* Combines the differential correction model of DifferentialPseudorangeFactor
336+
* with the lever arm compensation of PseudorangeFactorArm. Uses a Pose3
337+
* (position + attitude) as the receiver state variable.
338+
*
339+
* The error model is:
340+
* error = ||antenna_pos - satPos|| + c*(dt_u - dt_s) - pseudorange - correction
341+
*
342+
* @note Both the body pose and satellite position are expected in the ECEF
343+
* frame. Any conversion to/from a local navigation frame (NED/ENU) is
344+
* the caller's responsibility.
345+
*
346+
* @ingroup navigation
347+
*/
348+
class GTSAM_EXPORT DifferentialPseudorangeFactorArm
349+
: public NoiseModelFactorN<Pose3, double, double>,
350+
private PseudorangeBase {
351+
private:
352+
typedef NoiseModelFactorN<Pose3, double, double> Base;
353+
354+
Point3 bL_; ///< Lever arm from body origin to antenna in body frame.
355+
356+
public:
357+
// Provide access to the Matrix& version of evaluateError:
358+
using Base::evaluateError;
359+
360+
/// shorthand for a smart pointer to a factor
361+
typedef std::shared_ptr<DifferentialPseudorangeFactorArm> shared_ptr;
362+
363+
/// Typedef to this class
364+
typedef DifferentialPseudorangeFactorArm This;
365+
366+
/** default constructor - only use for serialization */
367+
DifferentialPseudorangeFactorArm() : PseudorangeBase{0.0, Point3(0, 0, 0), 0.0}, bL_(0, 0, 0) {}
368+
369+
virtual ~DifferentialPseudorangeFactorArm() = default;
370+
371+
/**
372+
* Construct a DifferentialPseudorangeFactorArm.
373+
*
374+
* @param ecefTbodyKey Receiver gtsam::Pose3 key (body pose in ECEF frame).
375+
* @param receiverClockBiasKey Receiver clock bias node.
376+
* @param differentialCorrectionKey Differential correction node.
377+
* @param measuredPseudorange Receiver-measured pseudorange in meters.
378+
* @param satellitePosition Satellite ECEF position in meters.
379+
* @param leverArm Translation from body origin to antenna in body frame.
380+
* @param satelliteClockBias Satellite clock bias in seconds.
381+
* @param model 1-D pseudorange noise model.
382+
*/
383+
DifferentialPseudorangeFactorArm(
384+
Key ecefTbodyKey, Key receiverClockBiasKey, Key differentialCorrectionKey,
385+
double measuredPseudorange, const Point3& satellitePosition,
386+
const Point3& leverArm, double satelliteClockBias = 0.0,
387+
const SharedNoiseModel& model = noiseModel::Unit::Create(1));
388+
389+
/// @return a deep copy of this factor
390+
gtsam::NonlinearFactor::shared_ptr clone() const override {
391+
return std::static_pointer_cast<gtsam::NonlinearFactor>(
392+
gtsam::NonlinearFactor::shared_ptr(new This(*this)));
393+
}
394+
395+
/// print
396+
void print(const std::string& s = "", const KeyFormatter& keyFormatter =
397+
DefaultKeyFormatter) const override;
398+
399+
/// equals
400+
bool equals(const NonlinearFactor& expected,
401+
double tol = 1e-9) const override;
402+
403+
/// vector of errors
404+
Vector evaluateError(const Pose3& ecef_T_body,
405+
const double& receiverClockBias,
406+
const double& differentialCorrection,
407+
OptionalMatrixType H_ecef_T_body,
408+
OptionalMatrixType HreceiverClockBias,
409+
OptionalMatrixType HdifferentialCorrection) const override;
410+
411+
/// return the lever arm, a position in the body frame
412+
inline const Point3& leverArm() const { return bL_; }
413+
414+
private:
415+
#if GTSAM_ENABLE_BOOST_SERIALIZATION ///
416+
/// Serialization function
417+
friend class boost::serialization::access;
418+
template <class ARCHIVE>
419+
void serialize(ARCHIVE& ar, const unsigned int /*version*/) {
420+
ar& BOOST_SERIALIZATION_BASE_OBJECT_NVP(
421+
DifferentialPseudorangeFactorArm::Base);
422+
ar& BOOST_SERIALIZATION_NVP(pseudorange_);
423+
ar& BOOST_SERIALIZATION_NVP(satPos_);
424+
ar& BOOST_SERIALIZATION_NVP(satClkBias_);
425+
ar& BOOST_SERIALIZATION_NVP(bL_);
426+
}
427+
#endif
428+
};
429+
430+
/// traits
431+
template <>
432+
struct traits<DifferentialPseudorangeFactorArm>
433+
: public Testable<DifferentialPseudorangeFactorArm> {};
434+
228435
} // namespace gtsam

0 commit comments

Comments
 (0)